diff --git a/Cs_HMI/AGVCSharp.sln b/Cs_HMI/AGVCSharp.sln
index 2e7614e..013a4b9 100644
--- a/Cs_HMI/AGVCSharp.sln
+++ b/Cs_HMI/AGVCSharp.sln
@@ -27,10 +27,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ENIGProtocol", "SubProject\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AGV4", "Project\AGV4.csproj", "{D6B3880D-7D5C-44E2-B6A5-CF6D881A8A38}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AGVMapEditor", "AGVMapEditor\AGVMapEditor.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AGVSimulator", "AGVSimulator\AGVSimulator.csproj", "{B2C3D4E5-0000-0000-0000-000000000000}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "솔루션 항목", "솔루션 항목", "{2A3A057F-5D22-31FD-628C-DF5EF75AEF1E}"
ProjectSection(SolutionItems) = preProject
build.bat = build.bat
@@ -40,10 +36,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "솔루션 항목", "솔루
TODO.md = TODO.md
EndProjectSection
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AGVNavigationCore", "AGVNavigationCore\AGVNavigationCore.csproj", "{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Logic", "Logic", "{E5C75D32-5AD6-44DD-8F27-E32023206EBB}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AGVNavigationCore", "AGVLogic\AGVNavigationCore\AGVNavigationCore.csproj", "{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AGVMapEditor", "AGVLogic\AGVMapEditor\AGVMapEditor.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AGVSimulator", "AGVLogic\AGVSimulator\AGVSimulator.csproj", "{B2C3D4E5-0000-0000-0000-000000000000}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test_Port", "TestProject\Test_Port\Test_Port.csproj", "{CCFA2CE7-A539-4ADC-B803-F759284C3463}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -174,6 +176,18 @@ Global
{D6B3880D-7D5C-44E2-B6A5-CF6D881A8A38}.Release|x64.Build.0 = Release|Any CPU
{D6B3880D-7D5C-44E2-B6A5-CF6D881A8A38}.Release|x86.ActiveCfg = Release|x86
{D6B3880D-7D5C-44E2-B6A5-CF6D881A8A38}.Release|x86.Build.0 = Release|x86
+ {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|x64.Build.0 = Debug|Any CPU
+ {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|x86.ActiveCfg = Debug|x86
+ {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|x86.Build.0 = Debug|x86
+ {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|x64.ActiveCfg = Release|Any CPU
+ {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|x64.Build.0 = Release|Any CPU
+ {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|x86.ActiveCfg = Release|x86
+ {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|x86.Build.0 = Release|x86
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -198,18 +212,18 @@ Global
{B2C3D4E5-0000-0000-0000-000000000000}.Release|x64.Build.0 = Release|Any CPU
{B2C3D4E5-0000-0000-0000-000000000000}.Release|x86.ActiveCfg = Release|Any CPU
{B2C3D4E5-0000-0000-0000-000000000000}.Release|x86.Build.0 = Release|Any CPU
- {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|x64.ActiveCfg = Debug|Any CPU
- {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|x64.Build.0 = Debug|Any CPU
- {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|x86.ActiveCfg = Debug|x86
- {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|x86.Build.0 = Debug|x86
- {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|Any CPU.Build.0 = Release|Any CPU
- {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|x64.ActiveCfg = Release|Any CPU
- {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|x64.Build.0 = Release|Any CPU
- {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|x86.ActiveCfg = Release|x86
- {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|x86.Build.0 = Release|x86
+ {CCFA2CE7-A539-4ADC-B803-F759284C3463}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CCFA2CE7-A539-4ADC-B803-F759284C3463}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CCFA2CE7-A539-4ADC-B803-F759284C3463}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CCFA2CE7-A539-4ADC-B803-F759284C3463}.Debug|x64.Build.0 = Debug|Any CPU
+ {CCFA2CE7-A539-4ADC-B803-F759284C3463}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CCFA2CE7-A539-4ADC-B803-F759284C3463}.Debug|x86.Build.0 = Debug|Any CPU
+ {CCFA2CE7-A539-4ADC-B803-F759284C3463}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CCFA2CE7-A539-4ADC-B803-F759284C3463}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CCFA2CE7-A539-4ADC-B803-F759284C3463}.Release|x64.ActiveCfg = Release|Any CPU
+ {CCFA2CE7-A539-4ADC-B803-F759284C3463}.Release|x64.Build.0 = Release|Any CPU
+ {CCFA2CE7-A539-4ADC-B803-F759284C3463}.Release|x86.ActiveCfg = Release|Any CPU
+ {CCFA2CE7-A539-4ADC-B803-F759284C3463}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -222,9 +236,10 @@ Global
{14E8C9A5-013E-49BA-B435-EFEFC77DD623} = {C423C39A-44E7-4F09-B2F7-7943975FF948}
{EB77976F-4DE4-46A5-8B25-D07226204C32} = {7AF32085-E7A6-4D06-BA6E-C6B1EBAEA99A}
{9365803B-933D-4237-93C7-B502C855A71C} = {C423C39A-44E7-4F09-B2F7-7943975FF948}
+ {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C} = {E5C75D32-5AD6-44DD-8F27-E32023206EBB}
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {E5C75D32-5AD6-44DD-8F27-E32023206EBB}
{B2C3D4E5-0000-0000-0000-000000000000} = {E5C75D32-5AD6-44DD-8F27-E32023206EBB}
- {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C} = {E5C75D32-5AD6-44DD-8F27-E32023206EBB}
+ {CCFA2CE7-A539-4ADC-B803-F759284C3463} = {7AF32085-E7A6-4D06-BA6E-C6B1EBAEA99A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B5B1FD72-356F-4840-83E8-B070AC21C8D9}
diff --git a/Cs_HMI/AGVLogic/AGVMapEditor/AGVMapEditor.csproj b/Cs_HMI/AGVLogic/AGVMapEditor/AGVMapEditor.csproj
index 66fc387..928fd1c 100644
--- a/Cs_HMI/AGVLogic/AGVMapEditor/AGVMapEditor.csproj
+++ b/Cs_HMI/AGVLogic/AGVMapEditor/AGVMapEditor.csproj
@@ -17,7 +17,7 @@
true
full
false
- bin\Debug\
+ ..\..\..\..\..\..\Amkor\AGV4\
DEBUG;TRACE
prompt
4
diff --git a/Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.cs b/Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.cs
index 9a9aeee..5c8dc98 100644
--- a/Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.cs
+++ b/Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.cs
@@ -111,6 +111,7 @@ namespace AGVMapEditor.Forms
// 이벤트 연결
_mapCanvas.NodeAdded += OnNodeAdded;
_mapCanvas.NodeSelected += OnNodeSelected;
+ _mapCanvas.NodesSelected += OnNodesSelected; // 다중 선택 이벤트
_mapCanvas.NodeMoved += OnNodeMoved;
_mapCanvas.NodeDeleted += OnNodeDeleted;
_mapCanvas.ConnectionDeleted += OnConnectionDeleted;
@@ -184,8 +185,46 @@ namespace AGVMapEditor.Forms
private void OnNodeSelected(object sender, MapNode node)
{
_selectedNode = node;
- UpdateNodeProperties();
- UpdateImageEditButton(); // 이미지 노드 선택 시 이미지 편집 버튼 활성화
+
+ if (node == null)
+ {
+ // 빈 공간 클릭 시 캔버스 속성 표시
+ ShowCanvasProperties();
+ }
+ else
+ {
+ // 노드 클릭 시 노드 속성 표시
+ UpdateNodeProperties();
+ UpdateImageEditButton(); // 이미지 노드 선택 시 이미지 편집 버튼 활성화
+ }
+ }
+
+ private void OnNodesSelected(object sender, List nodes)
+ {
+ // 다중 선택 시 처리
+ if (nodes == null || nodes.Count == 0)
+ {
+ ShowCanvasProperties();
+ return;
+ }
+
+ if (nodes.Count == 1)
+ {
+ // 단일 선택은 기존 방식 사용
+ _selectedNode = nodes[0];
+ UpdateNodeProperties();
+ UpdateImageEditButton();
+ }
+ else
+ {
+ // 다중 선택: 상태바에 선택 개수 표시
+ toolStripStatusLabel1.Text = $"{nodes.Count}개 노드 선택됨 - PropertyGrid에서 공통 속성 일괄 변경 가능";
+
+ // 다중 선택 PropertyWrapper 표시
+ var multiWrapper = new MultiNodePropertyWrapper(nodes);
+ _propertyGrid.SelectedObject = multiWrapper;
+ _propertyGrid.Focus();
+ }
}
private void OnNodeMoved(object sender, MapNode node)
@@ -589,6 +628,13 @@ namespace AGVMapEditor.Forms
_mapCanvas.Nodes = _mapNodes;
// RfidMappings 제거됨 - MapNode에 통합
+ // 🔥 맵 설정 적용 (배경색, 그리드 표시)
+ if (result.Settings != null)
+ {
+ _mapCanvas.BackColor = System.Drawing.Color.FromArgb(result.Settings.BackgroundColorArgb);
+ _mapCanvas.ShowGrid = result.Settings.ShowGrid;
+ }
+
// 현재 파일 경로 업데이트
_currentMapFile = filePath;
_hasChanges = false;
@@ -614,7 +660,38 @@ namespace AGVMapEditor.Forms
private void SaveMapToFile(string filePath)
{
- if (MapLoader.SaveMapToFile(filePath, _mapNodes))
+ // 🔥 백업 파일 생성 (기존 파일이 있을 경우)
+ if (File.Exists(filePath))
+ {
+ try
+ {
+ // 날짜시간 포함 백업 파일명 생성
+ var directory = Path.GetDirectoryName(filePath);
+ var fileNameWithoutExt = Path.GetFileNameWithoutExtension(filePath);
+ var extension = Path.GetExtension(filePath);
+ var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
+ var backupFileName = $"{fileNameWithoutExt}_{timestamp}{extension}.bak";
+ var backupFilePath = Path.Combine(directory, backupFileName);
+
+ // 기존 파일을 백업 파일로 복사
+ File.Copy(filePath, backupFilePath, true);
+ }
+ catch (Exception ex)
+ {
+ // 백업 파일 생성 실패 시 경고만 표시하고 계속 진행
+ MessageBox.Show($"백업 파일 생성 실패: {ex.Message}\n원본 파일은 계속 저장됩니다.", "백업 경고",
+ MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ }
+ }
+
+ // 🔥 현재 캔버스 설정을 맵 파일에 저장
+ var settings = new MapLoader.MapSettings
+ {
+ BackgroundColorArgb = _mapCanvas.BackColor.ToArgb(),
+ ShowGrid = _mapCanvas.ShowGrid
+ };
+
+ if (MapLoader.SaveMapToFile(filePath, _mapNodes, settings))
{
// 현재 파일 경로 업데이트
_currentMapFile = filePath;
@@ -892,7 +969,7 @@ namespace AGVMapEditor.Forms
{
if (_selectedNode == null)
{
- ClearNodeProperties();
+ ShowCanvasProperties();
return;
}
@@ -905,6 +982,16 @@ namespace AGVMapEditor.Forms
UpdateImageEditButton();
}
+ ///
+ /// 캔버스 속성 표시 (배경색 등)
+ ///
+ private void ShowCanvasProperties()
+ {
+ var canvasWrapper = new CanvasPropertyWrapper(_mapCanvas);
+ _propertyGrid.SelectedObject = canvasWrapper;
+ DisableImageEditButton();
+ }
+
private void ClearNodeProperties()
{
_propertyGrid.SelectedObject = null;
@@ -1010,6 +1097,9 @@ namespace AGVMapEditor.Forms
private void PropertyGrid_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
{
+ // 변경된 속성명 디버그 출력
+ System.Diagnostics.Debug.WriteLine($"[PropertyGrid] 속성 변경됨: {e.ChangedItem.PropertyDescriptor.Name}");
+
// RFID 값 변경시 중복 검사
if (e.ChangedItem.PropertyDescriptor.Name == "RFID")
{
@@ -1031,14 +1121,39 @@ namespace AGVMapEditor.Forms
_hasChanges = true;
UpdateTitle();
- // 현재 선택된 노드를 기억
+ // 🔥 다중 선택 여부 확인 및 선택된 노드들 저장
+ bool isMultiSelect = _propertyGrid.SelectedObject is MultiNodePropertyWrapper;
+ List selectedNodes = null;
+ if (isMultiSelect)
+ {
+ // 캔버스에서 현재 선택된 노드들 가져오기
+ selectedNodes = new List(_mapCanvas.SelectedNodes);
+ System.Diagnostics.Debug.WriteLine($"[PropertyGrid] 다중 선택 노드 수: {selectedNodes.Count}");
+ }
+
+ // 현재 선택된 노드를 기억 (단일 선택인 경우만)
var currentSelectedNode = _selectedNode;
-
+
RefreshNodeList();
RefreshMapCanvas();
-
- // 선택된 노드를 다시 선택
- if (currentSelectedNode != null)
+
+ // 🔥 캔버스 강제 갱신 (bool 타입 속성 변경 시 특히 필요)
+ _mapCanvas.Invalidate();
+ _mapCanvas.Update();
+
+ // 🔥 다중 선택인 경우 MultiNodePropertyWrapper를 다시 생성하여 바인딩
+ if (isMultiSelect && selectedNodes != null && selectedNodes.Count > 0)
+ {
+ System.Diagnostics.Debug.WriteLine($"[PropertyGrid] MultiNodePropertyWrapper 재생성: {selectedNodes.Count}개");
+ var multiWrapper = new MultiNodePropertyWrapper(selectedNodes);
+ _propertyGrid.SelectedObject = multiWrapper;
+ }
+
+ // PropertyGrid 새로고침
+ _propertyGrid.Refresh();
+
+ // 🔥 단일 선택인 경우에만 노드를 다시 선택 (다중 선택은 캔버스에서 유지)
+ if (!isMultiSelect && currentSelectedNode != null)
{
var nodeIndex = _mapNodes.IndexOf(currentSelectedNode);
if (nodeIndex >= 0)
@@ -1162,5 +1277,96 @@ namespace AGVMapEditor.Forms
}
}
+
+
+ #region Multi-Node Selection
+
+ private void ShowMultiNodeContextMenu(List 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();
+ }
+ }
+
+ ///
+ /// 선택된 노드들의 글자색 일괄 변경
+ ///
+ 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);
+ }
+ }
+ }
+
+ ///
+ /// 선택된 노드들의 배경색 일괄 변경
+ ///
+ 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
+
}
}
\ No newline at end of file
diff --git a/Cs_HMI/AGVLogic/AGVMapEditor/Models/NodePropertyWrapper.cs b/Cs_HMI/AGVLogic/AGVMapEditor/Models/NodePropertyWrapper.cs
index 412b0ed..04c53f7 100644
--- a/Cs_HMI/AGVLogic/AGVMapEditor/Models/NodePropertyWrapper.cs
+++ b/Cs_HMI/AGVLogic/AGVMapEditor/Models/NodePropertyWrapper.cs
@@ -4,6 +4,7 @@ using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using AGVNavigationCore.Models;
+using AGVNavigationCore.Controls;
namespace AGVMapEditor.Models
{
@@ -521,6 +522,84 @@ namespace AGVMapEditor.Models
}
}
+ [Category("표시")]
+ [DisplayName("노드 배경색")]
+ [Description("노드 배경 색상")]
+ public Color DisplayColor
+ {
+ get => _node.DisplayColor;
+ set
+ {
+ _node.DisplayColor = value;
+ _node.ModifiedDate = DateTime.Now;
+ }
+ }
+
+ [Category("표시")]
+ [DisplayName("글자 색상")]
+ [Description("노드 텍스트 색상 (NodeId, Name 등)")]
+ public Color ForeColor
+ {
+ get => _node.ForeColor;
+ set
+ {
+ _node.ForeColor = value;
+ _node.ModifiedDate = DateTime.Now;
+ }
+ }
+
+ [Category("표시")]
+ [DisplayName("글자 크기")]
+ [Description("노드 텍스트 크기 (픽셀)")]
+ public float TextFontSize
+ {
+ get => _node.TextFontSize;
+ set
+ {
+ _node.TextFontSize = Math.Max(5.0f, Math.Min(20.0f, value));
+ _node.ModifiedDate = DateTime.Now;
+ }
+ }
+
+ [Category("표시")]
+ [DisplayName("글자 굵게")]
+ [Description("노드 텍스트 볼드 표시")]
+ public bool TextFontBold
+ {
+ get => _node.TextFontBold;
+ set
+ {
+ _node.TextFontBold = value;
+ _node.ModifiedDate = DateTime.Now;
+ }
+ }
+
+ [Category("표시")]
+ [DisplayName("이름 말풍선 배경색")]
+ [Description("노드 이름 말풍선(하단 표시) 배경색")]
+ public Color NameBubbleBackColor
+ {
+ get => _node.NameBubbleBackColor;
+ set
+ {
+ _node.NameBubbleBackColor = value;
+ _node.ModifiedDate = DateTime.Now;
+ }
+ }
+
+ [Category("표시")]
+ [DisplayName("이름 말풍선 글자색")]
+ [Description("노드 이름 말풍선(하단 표시) 글자색")]
+ public Color NameBubbleForeColor
+ {
+ get => _node.NameBubbleForeColor;
+ set
+ {
+ _node.NameBubbleForeColor = value;
+ _node.ModifiedDate = DateTime.Now;
+ }
+ }
+
[Category("정보")]
[DisplayName("생성 일시")]
[Description("노드가 생성된 일시")]
@@ -540,5 +619,207 @@ namespace AGVMapEditor.Models
}
}
+ ///
+ /// 다중 노드 선택 시 공통 속성 편집용 래퍼
+ ///
+ public class MultiNodePropertyWrapper
+ {
+ private List _nodes;
+
+ public MultiNodePropertyWrapper(List nodes)
+ {
+ _nodes = nodes ?? new List();
+ }
+
+ [Category("다중 선택")]
+ [DisplayName("선택된 노드 수")]
+ [Description("현재 선택된 노드의 개수")]
+ [ReadOnly(true)]
+ public int SelectedCount => _nodes.Count;
+
+ [Category("표시")]
+ [DisplayName("노드 배경색")]
+ [Description("선택된 모든 노드의 배경색을 일괄 변경")]
+ public Color DisplayColor
+ {
+ get => _nodes.Count > 0 ? _nodes[0].DisplayColor : Color.Blue;
+ set
+ {
+ foreach (var node in _nodes)
+ {
+ node.DisplayColor = value;
+ node.ModifiedDate = DateTime.Now;
+ }
+ }
+ }
+
+ [Category("표시")]
+ [DisplayName("글자 색상")]
+ [Description("선택된 모든 노드의 글자 색상을 일괄 변경")]
+ public Color ForeColor
+ {
+ get => _nodes.Count > 0 ? _nodes[0].ForeColor : Color.Black;
+ set
+ {
+ foreach (var node in _nodes)
+ {
+ node.ForeColor = value;
+ node.ModifiedDate = DateTime.Now;
+ }
+ }
+ }
+
+ [Category("표시")]
+ [DisplayName("글자 크기")]
+ [Description("선택된 모든 노드의 글자 크기를 일괄 변경 (5~20 픽셀)")]
+ public float TextFontSize
+ {
+ get => _nodes.Count > 0 ? _nodes[0].TextFontSize : 7.0f;
+ set
+ {
+ var validValue = Math.Max(5.0f, Math.Min(20.0f, value));
+ System.Diagnostics.Debug.WriteLine($"[MultiNode] TextFontSize 변경 시작: {validValue}, 노드 수: {_nodes.Count}");
+
+ foreach (var node in _nodes)
+ {
+ System.Diagnostics.Debug.WriteLine($" - 노드 {node.NodeId}: {node.TextFontSize} → {validValue}");
+ node.TextFontSize = validValue;
+ node.ModifiedDate = DateTime.Now;
+ System.Diagnostics.Debug.WriteLine($" - 변경 후: {node.TextFontSize}");
+ }
+
+ System.Diagnostics.Debug.WriteLine($"[MultiNode] TextFontSize 변경 완료");
+ }
+ }
+
+ [Category("표시")]
+ [DisplayName("글자 굵게")]
+ [Description("선택된 모든 노드의 글자 굵기를 일괄 변경")]
+ public bool TextFontBold
+ {
+ get
+ {
+ var result = _nodes.Count > 0 ? _nodes[0].TextFontBold : true;
+ System.Diagnostics.Debug.WriteLine($"[MultiNode] TextFontBold GET: {result}");
+ return result;
+ }
+ set
+ {
+ System.Diagnostics.Debug.WriteLine($"[MultiNode] TextFontBold 변경 시작: {value}, 노드 수: {_nodes.Count}");
+
+ foreach (var node in _nodes)
+ {
+ System.Diagnostics.Debug.WriteLine($" - 노드 {node.NodeId}: {node.TextFontBold} → {value}");
+ node.TextFontBold = value;
+ node.ModifiedDate = DateTime.Now;
+ System.Diagnostics.Debug.WriteLine($" - 변경 후: {node.TextFontBold}");
+ }
+
+ System.Diagnostics.Debug.WriteLine($"[MultiNode] TextFontBold 변경 완료");
+ }
+ }
+
+ [Category("표시")]
+ [DisplayName("이름 말풍선 배경색")]
+ [Description("선택된 모든 노드의 이름 말풍선(하단 표시) 배경색을 일괄 변경")]
+ public Color NameBubbleBackColor
+ {
+ get => _nodes.Count > 0 ? _nodes[0].NameBubbleBackColor : Color.Gold;
+ set
+ {
+ foreach (var node in _nodes)
+ {
+ node.NameBubbleBackColor = value;
+ node.ModifiedDate = DateTime.Now;
+ }
+ }
+ }
+
+ [Category("표시")]
+ [DisplayName("이름 말풍선 글자색")]
+ [Description("선택된 모든 노드의 이름 말풍선(하단 표시) 글자색을 일괄 변경")]
+ public Color NameBubbleForeColor
+ {
+ get => _nodes.Count > 0 ? _nodes[0].NameBubbleForeColor : Color.Black;
+ set
+ {
+ foreach (var node in _nodes)
+ {
+ node.NameBubbleForeColor = value;
+ node.ModifiedDate = DateTime.Now;
+ }
+ }
+ }
+
+ [Category("고급")]
+ [DisplayName("활성화")]
+ [Description("선택된 모든 노드의 활성화 상태를 일괄 변경")]
+ public bool IsActive
+ {
+ get => _nodes.Count > 0 ? _nodes[0].IsActive : true;
+ set
+ {
+ foreach (var node in _nodes)
+ {
+ node.IsActive = value;
+ node.ModifiedDate = DateTime.Now;
+ }
+ }
+ }
+
+ [Category("정보")]
+ [DisplayName("안내")]
+ [Description("다중 선택 시 공통 속성만 변경할 수 있습니다")]
+ [ReadOnly(true)]
+ public string HelpText => "위 속성을 변경하면 선택된 모든 노드에 일괄 적용됩니다.";
+ }
+
+ ///
+ /// 캔버스 속성 래퍼 (배경색 등 캔버스 전체 속성 편집)
+ ///
+ public class CanvasPropertyWrapper
+ {
+ private UnifiedAGVCanvas _canvas;
+
+ public CanvasPropertyWrapper(UnifiedAGVCanvas canvas)
+ {
+ _canvas = canvas;
+ }
+
+ [Category("배경")]
+ [DisplayName("배경색")]
+ [Description("맵 캔버스 배경색")]
+ public Color BackgroundColor
+ {
+ get => _canvas.BackColor;
+ set
+ {
+ _canvas.BackColor = value;
+ _canvas.Invalidate();
+ }
+ }
+
+ [Category("그리드")]
+ [DisplayName("그리드 표시")]
+ [Description("그리드 표시 여부")]
+ public bool ShowGrid
+ {
+ get => _canvas.ShowGrid;
+ set
+ {
+ _canvas.ShowGrid = value;
+ _canvas.Invalidate();
+ }
+ }
+
+ [Category("정보")]
+ [DisplayName("설명")]
+ [Description("맵 캔버스 전체 속성")]
+ [ReadOnly(true)]
+ public string Description
+ {
+ get => "빈 공간을 클릭하면 캔버스 전체 속성을 편집할 수 있습니다.";
+ }
+ }
}
\ No newline at end of file
diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs
index 6aa1681..f95703a 100644
--- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs
+++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs
@@ -26,6 +26,10 @@ namespace AGVNavigationCore.Controls
}
var g = e.Graphics;
+
+ // 🔥 배경색 그리기 (변환 행렬 적용 전에 전체 화면을 배경색으로 채움)
+ g.Clear(this.BackColor);
+
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.High;
@@ -77,7 +81,8 @@ namespace AGVNavigationCore.Controls
}
// UI 정보 그리기 (변환 없이)
- DrawUIInfo(g);
+ if (_showGrid)
+ DrawUIInfo(g);
}
private void DrawGrid(Graphics g)
@@ -689,8 +694,8 @@ namespace AGVNavigationCore.Controls
var pulseRect = new Rectangle(rect.X - 4, rect.Y - 4, rect.Width + 8, rect.Height + 8);
g.DrawEllipse(new Pen(Color.FromArgb(150, 0, 255, 255), 2) { DashStyle = DashStyle.Dash }, pulseRect);
}
- // 선택된 노드 강조
- else if (node == _selectedNode)
+ // 선택된 노드 강조 (단일 또는 다중)
+ else if (node == _selectedNode || (_selectedNodes != null && _selectedNodes.Contains(node)))
{
g.DrawEllipse(_selectedNodePen, rect);
}
@@ -938,19 +943,22 @@ namespace AGVNavigationCore.Controls
// 아래쪽에 표시할 값 (RFID 우선, 없으면 노드ID)
if (node.HasRfid())
{
- // RFID가 있는 경우: 순수 RFID 값만 표시 (진한 색상)
+ // RFID가 있는 경우: 순수 RFID 값만 표시 (노드 전경색 사용)
displayText = node.RfidId;
- textColor = Color.Black;
+ textColor = node.ForeColor;
}
else
{
- // RFID가 없는 경우: 노드 ID 표시 (연한 색상)
+ // RFID가 없는 경우: 노드 ID 표시 (노드 전경색의 50% 투명도)
displayText = node.NodeId;
- textColor = Color.Gray;
+ textColor = Color.FromArgb(128, node.ForeColor);
}
- var font = new Font("Arial", 7, FontStyle.Bold);
- var descFont = new Font("Arial", 8, FontStyle.Bold);
+ // 🔥 노드의 폰트 설정 사용 (0 이하일 경우 기본값 7.0f 사용)
+ var fontStyle = node.TextFontBold ? FontStyle.Bold : FontStyle.Regular;
+ var fontSize = node.TextFontSize > 0 ? node.TextFontSize : 7.0f;
+ var font = new Font("Arial", fontSize, fontStyle);
+ var descFont = new Font("Arial", fontSize + 1, fontStyle);
// 메인 텍스트 크기 측정
var textSize = g.MeasureString(displayText, font);
@@ -971,12 +979,8 @@ namespace AGVNavigationCore.Controls
// 설명 텍스트 그리기 (설명이 있는 경우에만)
if (!string.IsNullOrEmpty(descriptionText))
{
- // 노드 이름 입력 여부에 따라 색상 구분
- // 입력된 경우: 진한 색상 (잘 보이게)
- // 기본값인 경우: 흐린 색상 (현재처럼)
- Color descColor = string.IsNullOrEmpty(node.Name)
- ? Color.FromArgb(120, Color.Black) // 입력 안됨: 흐린 색상
- : Color.FromArgb(200, Color.Black); // 입력됨: 진한 색상
+ // 🔥 노드의 말풍선 글자색 사용 (NameBubbleForeColor)
+ Color descColor = node.NameBubbleForeColor;
var rectpaddingx = 4;
var rectpaddingy = 2;
@@ -985,10 +989,10 @@ namespace AGVNavigationCore.Controls
(int)descSize.Width + rectpaddingx * 2,
(int)descSize.Height + rectpaddingy * 2);
- // 라운드 사각형 그리기 (빨간 배경)
- using (var backgroundBrush = new SolidBrush(Color.Gold))
+ // 라운드 사각형 그리기 (노드 이름 말풍선 배경색 사용)
+ using (var backgroundBrush = new SolidBrush(node.NameBubbleBackColor))
{
- DrawRoundedRectangle(g, backgroundBrush, roundRect, 3); // 모서리 반지름 6px
+ DrawRoundedRectangle(g, backgroundBrush, roundRect, 3); // 모서리 반지름 3px
}
// 라운드 사각형 테두리 그리기 (진한 빨간색)
@@ -1287,26 +1291,19 @@ namespace AGVNavigationCore.Controls
private Brush GetNodeBrush(MapNode node)
{
- // RFID가 없는 노드는 회색 계통으로 표시
+ // 🔥 노드의 DisplayColor를 배경색으로 사용
+ // RFID가 없는 노드는 DisplayColor를 50% 투명도로 표시
bool hasRfid = node.HasRfid();
- switch (node.Type)
+ Color bgColor = node.DisplayColor;
+
+ // RFID가 없는 경우 투명도 50%
+ if (!hasRfid)
{
- case NodeType.Normal:
- return hasRfid ? _normalNodeBrush : new SolidBrush(Color.LightGray);
- case NodeType.Rotation:
- return hasRfid ? _rotationNodeBrush : new SolidBrush(Color.DarkGray);
- case NodeType.Docking:
- return hasRfid ? _dockingNodeBrush : new SolidBrush(Color.Gray);
- case NodeType.Charging:
- return hasRfid ? _chargingNodeBrush : new SolidBrush(Color.Silver);
- case NodeType.Label:
- return new SolidBrush(Color.Purple);
- case NodeType.Image:
- return new SolidBrush(Color.Brown);
- default:
- return hasRfid ? _normalNodeBrush : new SolidBrush(Color.LightGray);
+ bgColor = Color.FromArgb(128, bgColor);
}
+
+ return new SolidBrush(bgColor);
}
private void DrawAGVs(Graphics g)
diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs
index ae488d0..3101c86 100644
--- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs
+++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs
@@ -17,6 +17,54 @@ namespace AGVNavigationCore.Controls
var worldPoint = ScreenToWorld(e.Location);
var hitNode = GetNodeAt(worldPoint);
+ // 🔥 어떤 모드에서든 노드/빈 공간 클릭 시 선택 이벤트 발생 (속성창 업데이트)
+ bool ctrlPressed = (ModifierKeys & Keys.Control) == Keys.Control;
+
+ if (hitNode != null)
+ {
+ // 노드 클릭
+ if (ctrlPressed && _editMode == EditMode.Select)
+ {
+ // Ctrl+클릭: 다중 선택 토글
+ if (_selectedNodes.Contains(hitNode))
+ {
+ _selectedNodes.Remove(hitNode);
+ }
+ else
+ {
+ _selectedNodes.Add(hitNode);
+ }
+
+ // 마지막 선택된 노드 업데이트 (단일 참조용)
+ _selectedNode = _selectedNodes.Count > 0 ? _selectedNodes[_selectedNodes.Count - 1] : null;
+
+ // 다중 선택 이벤트만 발생 (OnNodesSelected에서 단일/다중 구분 처리)
+ NodesSelected?.Invoke(this, _selectedNodes);
+ Invalidate();
+ }
+ else
+ {
+ // 일반 클릭: 단일 선택
+ _selectedNode = hitNode;
+ _selectedNodes.Clear();
+ _selectedNodes.Add(hitNode);
+
+ // NodesSelected 이벤트만 발생 (OnNodesSelected에서 단일/다중 구분 처리)
+ NodesSelected?.Invoke(this, _selectedNodes);
+ Invalidate();
+ }
+ }
+ else if (_editMode == EditMode.Select)
+ {
+ // 빈 공간 클릭 (Select 모드에서만) - 선택 해제
+ _selectedNode = null;
+ _selectedNodes.Clear();
+
+ // NodesSelected 이벤트만 발생 (OnNodesSelected에서 빈 리스트 처리)
+ NodesSelected?.Invoke(this, _selectedNodes);
+ Invalidate();
+ }
+
switch (_editMode)
{
case EditMode.Select:
@@ -76,7 +124,10 @@ namespace AGVNavigationCore.Controls
default:
// 기본 동작: 노드 선택 이벤트 발생
- NodeSelected?.Invoke(this, hitNode);
+ _selectedNode = hitNode;
+ _selectedNodes.Clear();
+ _selectedNodes.Add(hitNode);
+ NodesSelected?.Invoke(this, _selectedNodes);
break;
}
}
@@ -98,8 +149,11 @@ namespace AGVNavigationCore.Controls
Invalidate();
}
- // 노드 선택 이벤트도 발생 (속성창 업데이트)
- NodeSelected?.Invoke(this, node);
+ // 더블클릭 시 해당 노드만 선택 (다중 선택 해제)
+ _selectedNode = node;
+ _selectedNodes.Clear();
+ _selectedNodes.Add(node);
+ NodesSelected?.Invoke(this, _selectedNodes);
}
private void HandleLabelNodeDoubleClick(MapNode node)
@@ -118,14 +172,20 @@ namespace AGVNavigationCore.Controls
Invalidate();
}
- // 노드 선택 이벤트도 발생 (속성창 업데이트)
- NodeSelected?.Invoke(this, node);
+ // 더블클릭 시 해당 노드만 선택 (다중 선택 해제)
+ _selectedNode = node;
+ _selectedNodes.Clear();
+ _selectedNodes.Add(node);
+ NodesSelected?.Invoke(this, _selectedNodes);
}
private void HandleImageNodeDoubleClick(MapNode node)
{
- // 이미지 노드는 선택 이벤트만 발생 (MainForm에서 이미지 편집 버튼 활성화됨)
- NodeSelected?.Invoke(this, node);
+ // 더블클릭 시 해당 노드만 선택 (다중 선택 해제)
+ _selectedNode = node;
+ _selectedNodes.Clear();
+ _selectedNodes.Add(node);
+ NodesSelected?.Invoke(this, _selectedNodes);
// 이미지 편집 이벤트 발생 (MainForm에서 처리)
ImageNodeDoubleClicked?.Invoke(this, node);
@@ -519,13 +579,8 @@ namespace AGVNavigationCore.Controls
{
if (hitNode != null)
{
- // 노드 선택
- if (hitNode != _selectedNode)
- {
- _selectedNode = hitNode;
- NodeSelected?.Invoke(this, hitNode);
- Invalidate();
- }
+ // 노드 선택은 위쪽 MouseClick에서 이미 처리됨 (NodesSelected 이벤트 발생)
+ // 여기서는 추가 처리 없음
}
else
{
@@ -565,10 +620,11 @@ namespace AGVNavigationCore.Controls
else
{
// 빈 공간 클릭 시 선택 해제
- if (_selectedNode != null)
+ if (_selectedNode != null || _selectedNodes.Count > 0)
{
_selectedNode = null;
- NodeSelected?.Invoke(this, null);
+ _selectedNodes.Clear();
+ NodesSelected?.Invoke(this, _selectedNodes);
Invalidate();
}
}
@@ -745,7 +801,13 @@ namespace AGVNavigationCore.Controls
if (hitNode != null)
{
- _contextMenu.Items.Add("노드 속성...", null, (s, e) => NodeSelected?.Invoke(this, hitNode));
+ _contextMenu.Items.Add("노드 속성...", null, (s, e) =>
+ {
+ _selectedNode = hitNode;
+ _selectedNodes.Clear();
+ _selectedNodes.Add(hitNode);
+ NodesSelected?.Invoke(this, _selectedNodes);
+ });
_contextMenu.Items.Add("노드 삭제", null, (s, e) => HandleDeleteClick(hitNode));
_contextMenu.Items.Add("-");
}
diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs
index ee9a9ad..3ea90f3 100644
--- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs
+++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs
@@ -64,6 +64,7 @@ namespace AGVNavigationCore.Controls
// 맵 데이터
private List _nodes;
private MapNode _selectedNode;
+ private List _selectedNodes; // 다중 선택
private MapNode _hoveredNode;
private MapNode _destinationNode;
@@ -95,6 +96,11 @@ namespace AGVNavigationCore.Controls
private Point _connectionEndPoint;
private int _mouseMoveCounter = 0; // 디버그용: MouseMove 실행 횟수
+ // 영역 선택 관련
+ private bool _isAreaSelecting;
+ private Point _areaSelectStart;
+ private Point _areaSelectEnd;
+
// 그리드 및 줌 관련
private bool _showGrid = true;
private float _zoomFactor = 1.0f;
@@ -141,6 +147,7 @@ namespace AGVNavigationCore.Controls
// 맵 편집 이벤트
public event EventHandler NodeAdded;
public event EventHandler NodeSelected;
+ public event EventHandler> NodesSelected; // 다중 선택 이벤트
public event EventHandler NodeDeleted;
public event EventHandler NodeMoved;
public event EventHandler<(MapNode From, MapNode To)> ConnectionDeleted;
@@ -212,10 +219,15 @@ namespace AGVNavigationCore.Controls
}
///
- /// 선택된 노드
+ /// 선택된 노드 (단일)
///
public MapNode SelectedNode => _selectedNode;
+ ///
+ /// 선택된 노드들 (다중)
+ ///
+ public List SelectedNodes => _selectedNodes ?? new List();
+
///
/// 노드 목록
///
@@ -365,6 +377,7 @@ namespace AGVNavigationCore.Controls
ControlStyles.ResizeRedraw, true);
_nodes = new List();
+ _selectedNodes = new List(); // 다중 선택 리스트 초기화
_agvList = new List();
_agvPositions = new Dictionary();
_agvDirections = new Dictionary();
diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapLoader.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapLoader.cs
index fd9f912..1857126 100644
--- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapLoader.cs
+++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapLoader.cs
@@ -12,6 +12,15 @@ namespace AGVNavigationCore.Models
///
public static class MapLoader
{
+ ///
+ /// 맵 설정 정보 (배경색, 그리드 표시 등)
+ ///
+ public class MapSettings
+ {
+ public int BackgroundColorArgb { get; set; } = System.Drawing.Color.White.ToArgb();
+ public bool ShowGrid { get; set; } = true;
+ }
+
///
/// 맵 파일 로딩 결과
///
@@ -19,6 +28,7 @@ namespace AGVNavigationCore.Models
{
public bool Success { get; set; }
public List Nodes { get; set; } = new List();
+ public MapSettings Settings { get; set; } = new MapSettings();
public string ErrorMessage { get; set; } = string.Empty;
public string Version { get; set; } = string.Empty;
public DateTime CreatedDate { get; set; }
@@ -30,8 +40,9 @@ namespace AGVNavigationCore.Models
public class MapFileData
{
public List Nodes { get; set; } = new List();
+ public MapSettings Settings { get; set; } = new MapSettings();
public DateTime CreatedDate { get; set; }
- public string Version { get; set; } = "1.0";
+ public string Version { get; set; } = "1.1"; // 버전 업그레이드 (설정 추가)
}
///
@@ -66,6 +77,7 @@ namespace AGVNavigationCore.Models
if (mapData != null)
{
result.Nodes = mapData.Nodes ?? new List();
+ result.Settings = mapData.Settings ?? new MapSettings(); // 설정 로드
result.Version = mapData.Version ?? "1.0";
result.CreatedDate = mapData.CreatedDate;
@@ -111,8 +123,9 @@ namespace AGVNavigationCore.Models
///
/// 저장할 파일 경로
/// 맵 노드 목록
+ /// 맵 설정 (배경색, 그리드 표시 등)
/// 저장 성공 여부
- public static bool SaveMapToFile(string filePath, List nodes)
+ public static bool SaveMapToFile(string filePath, List nodes, MapSettings settings = null)
{
try
{
@@ -122,8 +135,9 @@ namespace AGVNavigationCore.Models
var mapData = new MapFileData
{
Nodes = nodes,
+ Settings = settings ?? new MapSettings(), // 설정 저장
CreatedDate = DateTime.Now,
- Version = "1.0"
+ Version = "1.1"
};
var json = JsonConvert.SerializeObject(mapData, Formatting.Indented);
diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs
index 4d26d06..496a842 100644
--- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs
+++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs
@@ -124,7 +124,7 @@ namespace AGVNavigationCore.Models
public FontStyle FontStyle { get; set; } = FontStyle.Regular;
///
- /// 라벨 전경색 (NodeType.Label인 경우 사용)
+ /// 텍스트 전경색 (모든 노드 타입에서 사용)
///
public Color ForeColor { get; set; } = Color.Black;
@@ -133,6 +133,33 @@ namespace AGVNavigationCore.Models
///
public Color BackColor { get; set; } = Color.Transparent;
+ private float _textFontSize = 7.0f;
+
+ ///
+ /// 텍스트 폰트 크기 (모든 노드 타입의 텍스트 표시에 사용, 픽셀 단위)
+ /// 0 이하의 값이 설정되면 기본값 7.0f로 자동 설정
+ ///
+ public float TextFontSize
+ {
+ get => _textFontSize;
+ set => _textFontSize = value > 0 ? value : 7.0f;
+ }
+
+ ///
+ /// 텍스트 볼드체 여부 (모든 노드 타입의 텍스트 표시에 사용)
+ ///
+ public bool TextFontBold { get; set; } = true;
+
+ ///
+ /// 노드 이름 말풍선 배경색 (하단에 표시되는 노드 이름의 배경색)
+ ///
+ public Color NameBubbleBackColor { get; set; } = Color.Gold;
+
+ ///
+ /// 노드 이름 말풍선 글자색 (하단에 표시되는 노드 이름의 글자색)
+ ///
+ public Color NameBubbleForeColor { get; set; } = Color.Black;
+
///
/// 라벨 배경 표시 여부 (NodeType.Label인 경우 사용)
///
@@ -347,6 +374,10 @@ namespace AGVNavigationCore.Models
FontStyle = FontStyle,
ForeColor = ForeColor,
BackColor = BackColor,
+ TextFontSize = TextFontSize,
+ TextFontBold = TextFontBold,
+ NameBubbleBackColor = NameBubbleBackColor,
+ NameBubbleForeColor = NameBubbleForeColor,
ShowBackground = ShowBackground,
Padding = Padding,
ImagePath = ImagePath,
diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs
index 848a453..ec7cc2f 100644
--- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs
+++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs
@@ -306,15 +306,15 @@ namespace AGVNavigationCore.Models
}
// 5. 방향체크
- if(CurrentDirection != TargetNode.MotorDirection)
- {
- return new AGVCommand(
- MotorCommand.Stop,
- MagnetPosition.S,
- SpeedLevel.L,
- $"(재탐색요청)모터방향 불일치 현재위치:{_currentNode.NodeId}"
- );
- }
+ //if(CurrentDirection != TargetNode.MotorDirection)
+ //{
+ // return new AGVCommand(
+ // MotorCommand.Stop,
+ // MagnetPosition.S,
+ // SpeedLevel.L,
+ // $"(재탐색요청)모터방향 불일치 현재위치:{_currentNode.NodeId}"
+ // );
+ //}
//this.CurrentNodeId
diff --git a/Cs_HMI/AGVLogic/AGVSimulator/AGVSimulator.csproj b/Cs_HMI/AGVLogic/AGVSimulator/AGVSimulator.csproj
index c79da87..2ff5628 100644
--- a/Cs_HMI/AGVLogic/AGVSimulator/AGVSimulator.csproj
+++ b/Cs_HMI/AGVLogic/AGVSimulator/AGVSimulator.csproj
@@ -17,7 +17,7 @@
true
full
false
- bin\Debug\
+ ..\..\..\..\..\..\Amkor\AGV4\
DEBUG;TRACE
prompt
4
diff --git a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.Designer.cs b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.Designer.cs
index 8f33df4..13b8e30 100644
--- a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.Designer.cs
+++ b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.Designer.cs
@@ -51,6 +51,8 @@ namespace AGVSimulator.Forms
this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.openMapToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.reloadMapToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.맵저장SToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.맵다른이름으로저장ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
this.launchMapEditorToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator();
@@ -65,7 +67,6 @@ namespace AGVSimulator.Forms
this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this._toolStrip = new System.Windows.Forms.ToolStrip();
- this.openMapToolStripButton = new System.Windows.Forms.ToolStripButton();
this.reloadMapToolStripButton = new System.Windows.Forms.ToolStripButton();
this.launchMapEditorToolStripButton = new System.Windows.Forms.ToolStripButton();
this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
@@ -79,6 +80,7 @@ namespace AGVSimulator.Forms
this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator();
this.toolStripButton1 = new System.Windows.Forms.ToolStripButton();
this.btPredict = new System.Windows.Forms.ToolStripButton();
+ this.btMakeMap = new System.Windows.Forms.ToolStripButton();
this._statusStrip = new System.Windows.Forms.StatusStrip();
this._statusLabel = new System.Windows.Forms.ToolStripStatusLabel();
this._coordLabel = new System.Windows.Forms.ToolStripStatusLabel();
@@ -109,12 +111,12 @@ namespace AGVSimulator.Forms
this._addAgvButton = new System.Windows.Forms.Button();
this._agvListCombo = new System.Windows.Forms.ComboBox();
this._canvasPanel = new System.Windows.Forms.Panel();
+ this.lbPredict = new System.Windows.Forms.RichTextBox();
this._agvInfoPanel = new System.Windows.Forms.Panel();
this._pathDebugLabel = new System.Windows.Forms.TextBox();
this._agvInfoTitleLabel = new System.Windows.Forms.Label();
this._liftDirectionLabel = new System.Windows.Forms.Label();
this._motorDirectionLabel = new System.Windows.Forms.Label();
- this.lbPredict = new System.Windows.Forms.RichTextBox();
this.timer1 = new System.Windows.Forms.Timer(this.components);
this._menuStrip.SuspendLayout();
this._toolStrip.SuspendLayout();
@@ -145,6 +147,8 @@ namespace AGVSimulator.Forms
this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.openMapToolStripMenuItem,
this.reloadMapToolStripMenuItem,
+ this.맵저장SToolStripMenuItem,
+ this.맵다른이름으로저장ToolStripMenuItem,
this.toolStripSeparator1,
this.launchMapEditorToolStripMenuItem,
this.toolStripSeparator4,
@@ -169,6 +173,20 @@ namespace AGVSimulator.Forms
this.reloadMapToolStripMenuItem.Text = "맵 다시열기(&R)";
this.reloadMapToolStripMenuItem.Click += new System.EventHandler(this.OnReloadMap_Click);
//
+ // 맵저장SToolStripMenuItem
+ //
+ this.맵저장SToolStripMenuItem.Name = "맵저장SToolStripMenuItem";
+ this.맵저장SToolStripMenuItem.Size = new System.Drawing.Size(221, 22);
+ this.맵저장SToolStripMenuItem.Text = "맵 저장(&S)";
+ this.맵저장SToolStripMenuItem.Click += new System.EventHandler(this.맵저장SToolStripMenuItem_Click);
+ //
+ // 맵다른이름으로저장ToolStripMenuItem
+ //
+ this.맵다른이름으로저장ToolStripMenuItem.Name = "맵다른이름으로저장ToolStripMenuItem";
+ this.맵다른이름으로저장ToolStripMenuItem.Size = new System.Drawing.Size(221, 22);
+ this.맵다른이름으로저장ToolStripMenuItem.Text = "맵 다른 이름으로 저장";
+ this.맵다른이름으로저장ToolStripMenuItem.Click += new System.EventHandler(this.btMapSaveAs_Click);
+ //
// toolStripSeparator1
//
this.toolStripSeparator1.Name = "toolStripSeparator1";
@@ -272,7 +290,6 @@ namespace AGVSimulator.Forms
// _toolStrip
//
this._toolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.openMapToolStripButton,
this.reloadMapToolStripButton,
this.launchMapEditorToolStripButton,
this.toolStripSeparator2,
@@ -285,22 +302,14 @@ namespace AGVSimulator.Forms
this.resetZoomToolStripButton,
this.toolStripSeparator5,
this.toolStripButton1,
- this.btPredict});
+ this.btPredict,
+ this.btMakeMap});
this._toolStrip.Location = new System.Drawing.Point(0, 24);
this._toolStrip.Name = "_toolStrip";
this._toolStrip.Size = new System.Drawing.Size(1034, 25);
this._toolStrip.TabIndex = 1;
this._toolStrip.Text = "toolStrip";
//
- // openMapToolStripButton
- //
- this.openMapToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
- this.openMapToolStripButton.Name = "openMapToolStripButton";
- this.openMapToolStripButton.Size = new System.Drawing.Size(51, 22);
- this.openMapToolStripButton.Text = "맵 열기";
- this.openMapToolStripButton.ToolTipText = "맵 파일을 엽니다";
- this.openMapToolStripButton.Click += new System.EventHandler(this.OnOpenMap_Click);
- //
// reloadMapToolStripButton
//
this.reloadMapToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
@@ -406,6 +415,15 @@ namespace AGVSimulator.Forms
this.btPredict.Text = "다음 행동 예측";
this.btPredict.Click += new System.EventHandler(this.btPredict_Click);
//
+ // btMakeMap
+ //
+ this.btMakeMap.Image = ((System.Drawing.Image)(resources.GetObject("btMakeMap.Image")));
+ this.btMakeMap.ImageTransparentColor = System.Drawing.Color.Magenta;
+ this.btMakeMap.Name = "btMakeMap";
+ this.btMakeMap.Size = new System.Drawing.Size(63, 22);
+ this.btMakeMap.Text = "맵기록";
+ this.btMakeMap.Click += new System.EventHandler(this.btMakeMap_Click);
+ //
// _statusStrip
//
this._statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
@@ -701,6 +719,15 @@ namespace AGVSimulator.Forms
this._canvasPanel.Size = new System.Drawing.Size(801, 560);
this._canvasPanel.TabIndex = 4;
//
+ // lbPredict
+ //
+ this.lbPredict.Dock = System.Windows.Forms.DockStyle.Bottom;
+ this.lbPredict.Location = new System.Drawing.Point(0, 513);
+ this.lbPredict.Name = "lbPredict";
+ this.lbPredict.Size = new System.Drawing.Size(801, 47);
+ this.lbPredict.TabIndex = 0;
+ this.lbPredict.Text = "";
+ //
// _agvInfoPanel
//
this._agvInfoPanel.BackColor = System.Drawing.Color.LightBlue;
@@ -757,15 +784,6 @@ namespace AGVSimulator.Forms
this._motorDirectionLabel.TabIndex = 2;
this._motorDirectionLabel.Text = "모터 방향: -";
//
- // lbPredict
- //
- this.lbPredict.Dock = System.Windows.Forms.DockStyle.Bottom;
- this.lbPredict.Location = new System.Drawing.Point(0, 513);
- this.lbPredict.Name = "lbPredict";
- this.lbPredict.Size = new System.Drawing.Size(801, 47);
- this.lbPredict.TabIndex = 0;
- this.lbPredict.Text = "";
- //
// timer1
//
this.timer1.Interval = 500;
@@ -825,7 +843,6 @@ namespace AGVSimulator.Forms
private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem;
private System.Windows.Forms.ToolStrip _toolStrip;
- private System.Windows.Forms.ToolStripButton openMapToolStripButton;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator2;
private System.Windows.Forms.ToolStripButton startSimulationToolStripButton;
private System.Windows.Forms.ToolStripButton stopSimulationToolStripButton;
@@ -879,5 +896,8 @@ namespace AGVSimulator.Forms
private System.Windows.Forms.ToolStripButton btPredict;
private System.Windows.Forms.RichTextBox lbPredict;
private System.Windows.Forms.Timer timer1;
+ private System.Windows.Forms.ToolStripButton btMakeMap;
+ private System.Windows.Forms.ToolStripMenuItem 맵저장SToolStripMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem 맵다른이름으로저장ToolStripMenuItem;
}
}
\ No newline at end of file
diff --git a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs
index 7ed36de..40b4b8a 100644
--- a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs
+++ b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs
@@ -35,6 +35,13 @@ namespace AGVSimulator.Forms
private string _currentMapFilePath;
private bool _isTargetCalcMode; // 타겟계산 모드 상태
+ // 맵 스캔 모드 관련
+ private bool _isMapScanMode; // 맵 스캔 모드 상태
+ private DateTime _lastNodeAddTime; // 마지막 노드 추가 시간
+ private MapNode _lastScannedNode; // 마지막으로 스캔된 노드
+ private int _scanNodeCounter; // 스캔 노드 카운터
+ private AgvDirection _lastScanDirection; // 마지막 스캔 방향
+
// UI Controls - Designer에서 생성됨
#endregion
@@ -238,7 +245,8 @@ namespace AGVSimulator.Forms
UpdateAGVComboBox();
UpdateUI();
- _statusLabel.Text = $"{agvId} 추가됨";
+ _statusLabel.Text = $"{agvId} 추가됨";
+ _simulatorCanvas.FitToNodes();
}
private void OnRemoveAGV_Click(object sender, EventArgs e)
@@ -508,6 +516,165 @@ namespace AGVSimulator.Forms
return closestNode;
}
+ ///
+ /// 방향을 기호로 변환
+ ///
+ private string GetDirectionSymbol(AgvDirection direction)
+ {
+ switch (direction)
+ {
+ case AgvDirection.Forward: return "→";
+ case AgvDirection.Backward: return "←";
+ case AgvDirection.Left: return "↺";
+ case AgvDirection.Right: return "↻";
+ default: return "-";
+ }
+ }
+
+ ///
+ /// 맵 스캔 모드에서 RFID로부터 노드 생성
+ ///
+ private void CreateNodeFromRfidScan(string rfidId, VirtualAGV selectedAGV)
+ {
+ try
+ {
+ // 현재 선택된 방향 확인 (최상단에서 먼저 확인)
+ var directionItem = _directionCombo.SelectedItem as DirectionItem;
+ var currentDirection = directionItem?.Direction ?? AgvDirection.Forward;
+
+ // 중복 RFID 확인
+ var existingNode = _mapNodes?.FirstOrDefault(n => n.RfidId.Equals(rfidId, StringComparison.OrdinalIgnoreCase));
+ if (existingNode != null)
+ {
+ // 이미 존재하는 노드로 이동
+ Program.WriteLine($"[맵 스캔] RFID '{rfidId}'는 이미 존재합니다 (노드: {existingNode.NodeId})");
+
+ // 기존 노드로 AGV 위치 설정
+ _simulatorCanvas.SetAGVPosition(selectedAGV.AgvId, existingNode, currentDirection);
+ selectedAGV.SetPosition(existingNode, currentDirection);
+
+ _lastScannedNode = existingNode;
+ _lastNodeAddTime = DateTime.Now;
+ _lastScanDirection = currentDirection; // 방향 업데이트
+
+ _statusLabel.Text = $"기존 노드로 이동: {existingNode.NodeId} [{GetDirectionSymbol(currentDirection)}]";
+ _rfidTextBox.Text = "";
+ return;
+ }
+
+ // 새 노드 생성 위치 계산
+ int newX = 100; // 기본 시작 X 위치
+ int newY = 300; // 기본 시작 Y 위치
+
+ if (_lastScannedNode != null)
+ {
+ // 시간차 기반 X축 거리 계산
+ var timeDiff = (DateTime.Now - _lastNodeAddTime).TotalSeconds;
+
+ // 10초당 10px, 최소 50px, 최대 100px
+ int distanceX = Math.Max(50, Math.Min(100, (int)(timeDiff * 10)));
+
+ // 방향 전환 확인
+ bool directionChanged = (_lastScanDirection != currentDirection);
+
+ if (directionChanged)
+ {
+ // 방향이 바뀌면 Y축을 50px 증가시켜서 겹치지 않게 함
+ newY = _lastScannedNode.Position.Y + 50;
+ newX = _lastScannedNode.Position.X; // X는 같은 위치에서 시작
+
+ Program.WriteLine($"[맵 스캔] 방향 전환: {_lastScanDirection} → {currentDirection}, Y축 +50px");
+ }
+ else
+ {
+ // 방향이 같으면 Y축 유지
+ newY = _lastScannedNode.Position.Y;
+
+ // 모터 방향에 따라 X축 증가/감소
+ if (currentDirection == AgvDirection.Forward)
+ {
+ // 전진: X축 증가
+ newX = _lastScannedNode.Position.X + distanceX;
+ Program.WriteLine($"[맵 스캔] 전진 모드: X축 +{distanceX}px");
+ }
+ else if (currentDirection == AgvDirection.Backward)
+ {
+ // 후진: X축 감소
+ newX = _lastScannedNode.Position.X - distanceX;
+ Program.WriteLine($"[맵 스캔] 후진 모드: X축 -{distanceX}px");
+ }
+ else
+ {
+ // 그 외(회전 등): 기본적으로 전진 방향 사용
+ newX = _lastScannedNode.Position.X + distanceX;
+ Program.WriteLine($"[맵 스캔] 기타 방향({currentDirection}): X축 +{distanceX}px");
+ }
+ }
+
+ Program.WriteLine($"[맵 스캔] 시간차: {timeDiff:F1}초 → 거리: {distanceX}px");
+ }
+
+ // 새 노드 생성
+ var newNodeId = $"{_scanNodeCounter:D3}";
+ var newNode = new MapNode
+ {
+ NodeId = newNodeId,
+ RfidId = rfidId,
+ Position = new Point(newX, newY),
+ Type = NodeType.Normal,
+ IsActive = true,
+ Name = $"N{_scanNodeCounter}"
+ };
+
+ // 맵에 추가
+ if (_mapNodes == null)
+ _mapNodes = new List();
+
+ _mapNodes.Add(newNode);
+
+ // 이전 노드와 연결 생성
+ if (_lastScannedNode != null)
+ {
+ // 양방향 연결 (ConnectedNodes에 추가 - JSON 저장됨)
+ _lastScannedNode.AddConnection(newNode.NodeId);
+ newNode.AddConnection(_lastScannedNode.NodeId);
+
+ Program.WriteLine($"[맵 스캔] 연결 생성: {_lastScannedNode.NodeId} ↔ {newNode.NodeId}");
+ }
+
+ // AGV 위치 설정
+ _simulatorCanvas.SetAGVPosition(selectedAGV.AgvId, newNode, currentDirection);
+ selectedAGV.SetPosition(newNode, currentDirection);
+
+ // 캔버스 업데이트
+ _simulatorCanvas.Nodes = _mapNodes;
+
+ // 화면을 새 노드 위치로 이동
+ _simulatorCanvas.PanToNode(newNode.NodeId);
+ _simulatorCanvas.Invalidate();
+
+ // 상태 업데이트
+ _lastScannedNode = newNode;
+ _lastNodeAddTime = DateTime.Now;
+ _lastScanDirection = currentDirection; // 현재 방향 저장
+ _scanNodeCounter++;
+
+ // UI 업데이트
+ UpdateNodeComboBoxes();
+
+ _statusLabel.Text = $"노드 생성: {newNode.NodeId} (RFID: {rfidId}) [{GetDirectionSymbol(currentDirection)}] - 총 {_mapNodes.Count}개";
+ _rfidTextBox.Text = "";
+
+ Program.WriteLine($"[맵 스캔] 노드 생성 완료: {newNode.NodeId} (RFID: {rfidId}) at ({newX}, {newY}), 방향: {currentDirection}");
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"노드 생성 중 오류 발생:\n{ex.Message}", "오류",
+ MessageBoxButtons.OK, MessageBoxIcon.Error);
+ Program.WriteLine($"[맵 스캔 오류] {ex.Message}");
+ }
+ }
+
private void OnSetPosition_Click(object sender, EventArgs e)
{
SetAGVPositionByRfid();
@@ -561,6 +728,19 @@ namespace AGVSimulator.Forms
MessageBox.Show("RFID 값을 입력해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
+
+ // 선택된 방향 확인
+ var selectedDirectionItem = _directionCombo.SelectedItem as DirectionItem;
+ var selectedDirection = selectedDirectionItem?.Direction ?? AgvDirection.Forward;
+
+
+ // 맵 스캔 모드일 때: 노드 자동 생성
+ if (_isMapScanMode)
+ {
+ CreateNodeFromRfidScan(rfidId, selectedAGV);
+ this._simulatorCanvas.FitToNodes();
+ return;
+ }
// RFID에 해당하는 노드 직접 찾기
var targetNode = _mapNodes?.FirstOrDefault(n => n.RfidId.Equals(rfidId, StringComparison.OrdinalIgnoreCase));
@@ -571,10 +751,7 @@ namespace AGVSimulator.Forms
return;
}
- // 선택된 방향 확인
- var selectedDirectionItem = _directionCombo.SelectedItem as DirectionItem;
- var selectedDirection = selectedDirectionItem?.Direction ?? AgvDirection.Forward;
-
+
//이전위치와 동일한지 체크한다.
if (selectedAGV.CurrentNodeId == targetNode.NodeId && selectedAGV.CurrentDirection == selectedDirection)
{
@@ -1625,6 +1802,184 @@ namespace AGVSimulator.Forms
var command = agv.Predict();
this.lbPredict.Text = $"Motor:{command.Motor},Magnet:{command.Magnet},Speed:{command.Speed} : {command.Reason}";
}
+
+
+ private void btMakeMap_Click(object sender, EventArgs e)
+ {
+ if (!_isMapScanMode)
+ {
+ // 스캔 모드 시작
+ var result = MessageBox.Show(
+ "맵 스캔 모드를 시작합니다.\n\n" +
+ "RFID를 입력하면 자동으로 맵 노드가 생성되고\n" +
+ "이전 노드와 연결됩니다.\n\n" +
+ "기존 맵 데이터를 삭제하고 시작하시겠습니까?\n\n" +
+ "예: 새 맵 시작\n" +
+ "아니오: 기존 맵에 추가",
+ "맵 스캔 모드",
+ MessageBoxButtons.YesNoCancel,
+ MessageBoxIcon.Question);
+
+ if (result == DialogResult.Cancel)
+ return;
+
+ if (result == DialogResult.Yes)
+ {
+ // 기존 맵 데이터 삭제
+ _mapNodes?.Clear();
+ _mapNodes = new List();
+ _simulatorCanvas.Nodes = _mapNodes;
+ _currentMapFilePath = string.Empty;
+ UpdateNodeComboBoxes();
+ _statusLabel.Text = "맵 초기화 완료 - 스캔 모드 시작";
+ }
+
+ // 스캔 모드 활성화
+ _isMapScanMode = true;
+ _lastNodeAddTime = DateTime.Now;
+ _lastScannedNode = null;
+ _scanNodeCounter = 1;
+ _lastScanDirection = AgvDirection.Forward; // 기본 방향은 전진
+ btMakeMap.Text = "스캔 중지";
+ btMakeMap.BackColor = Color.LightCoral;
+ _statusLabel.Text = "맵 스캔 모드: RFID를 입력하여 노드를 생성하세요";
+
+ Program.WriteLine("[맵 스캔] 스캔 모드 시작");
+ }
+ else
+ {
+ // 스캔 모드 종료
+ _isMapScanMode = false;
+ btMakeMap.Text = "맵 생성";
+ btMakeMap.BackColor = SystemColors.Control;
+ _statusLabel.Text = $"맵 스캔 완료 - {_mapNodes?.Count ?? 0}개 노드 생성됨";
+
+ Program.WriteLine($"[맵 스캔] 스캔 모드 종료 - 총 {_mapNodes?.Count ?? 0}개 노드");
+
+ // 맵 저장 권장
+ if (_mapNodes != null && _mapNodes.Count > 0)
+ {
+ var saveResult = MessageBox.Show(
+ $"맵 스캔이 완료되었습니다.\n\n" +
+ $"생성된 노드: {_mapNodes.Count}개\n\n" +
+ "맵을 저장하시겠습니까?",
+ "맵 저장",
+ MessageBoxButtons.YesNo,
+ MessageBoxIcon.Question);
+
+ if (saveResult == DialogResult.Yes)
+ {
+ btMapSaveAs_Click(sender, e);
+ }
+ }
+ }
+ }
+
+
+ ///
+ /// 맵 데이터를 파일에 저장 (MapLoader 공통 저장 로직 사용)
+ ///
+ private void SaveMapToFile(string filePath)
+ {
+ try
+ {
+ // MapLoader의 표준 저장 메서드 사용 (AGVMapEditor와 동일한 형식)
+ bool success = MapLoader.SaveMapToFile(filePath, _mapNodes);
+
+ if (success)
+ {
+ Program.WriteLine($"[맵 저장] 파일 저장 완료: {filePath} ({_mapNodes.Count}개 노드)");
+ }
+ else
+ {
+ throw new InvalidOperationException("맵 저장에 실패했습니다.");
+ }
+ }
+ catch (Exception ex)
+ {
+ Program.WriteLine($"[맵 저장 오류] {ex.Message}");
+ throw;
+ }
+ }
+
+ private void btMapSaveAs_Click(object sender, EventArgs e)
+ {
+ // 맵 데이터 확인
+ if (_mapNodes == null || _mapNodes.Count == 0)
+ {
+ MessageBox.Show("저장할 맵 데이터가 없습니다.", "알림",
+ MessageBoxButtons.OK, MessageBoxIcon.Information);
+ return;
+ }
+
+ using (var saveDialog = new SaveFileDialog())
+ {
+ saveDialog.Filter = "AGV Map Files (*.agvmap)|*.agvmap|모든 파일 (*.*)|*.*";
+ saveDialog.Title = "맵 파일 저장";
+ saveDialog.DefaultExt = "agvmap";
+
+ // 현재 파일이 있으면 기본 파일명으로 설정
+ if (!string.IsNullOrEmpty(_currentMapFilePath))
+ {
+ saveDialog.FileName = Path.GetFileName(_currentMapFilePath);
+ saveDialog.InitialDirectory = Path.GetDirectoryName(_currentMapFilePath);
+ }
+ else
+ {
+ // 기본 파일명: 날짜_시간 형식
+ saveDialog.FileName = $"ScanMap_{DateTime.Now:yyyyMMdd_HHmmss}.agvmap";
+ }
+
+ if (saveDialog.ShowDialog() == DialogResult.OK)
+ {
+ try
+ {
+ SaveMapToFile(saveDialog.FileName);
+ _currentMapFilePath = saveDialog.FileName;
+
+ // 설정에 마지막 맵 파일 경로 저장
+ _config.LastMapFilePath = _currentMapFilePath;
+ if (_config.AutoSave)
+ {
+ _config.Save();
+ }
+
+ _statusLabel.Text = $"맵 저장 완료: {Path.GetFileName(_currentMapFilePath)}";
+ MessageBox.Show($"맵이 저장되었습니다.\n\n파일: {_currentMapFilePath}", "저장 완료",
+ MessageBoxButtons.OK, MessageBoxIcon.Information);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"맵 저장 중 오류 발생:\n{ex.Message}", "저장 오류",
+ MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+ }
+ }
+
+ private void 맵저장SToolStripMenuItem_Click(object sender, EventArgs e)
+ {
+ // 현재 맵 파일 경로가 있는 경우 해당 파일에 저장
+ if (string.IsNullOrEmpty(_currentMapFilePath))
+ {
+ // 경로가 없으면 다른 이름으로 저장 다이얼로그 표시
+ btMapSaveAs_Click(sender, e);
+ return;
+ }
+
+ try
+ {
+ SaveMapToFile(_currentMapFilePath);
+ _statusLabel.Text = $"맵 저장 완료: {Path.GetFileName(_currentMapFilePath)}";
+ MessageBox.Show($"맵이 저장되었습니다.\n\n파일: {_currentMapFilePath}", "저장 완료",
+ MessageBoxButtons.OK, MessageBoxIcon.Information);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"맵 저장 중 오류 발생:\n{ex.Message}", "저장 오류",
+ MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
}
///
diff --git a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.resx b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.resx
index 5d703c1..94a377e 100644
--- a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.resx
+++ b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.resx
@@ -152,6 +152,21 @@
HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb
1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC
nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
+
+
+
+
+ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+ YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK
+ YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X
+ /aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t
+ I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM
+ cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh
+ 6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD
+ lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A
+ HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb
+ 1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC
+ nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
diff --git a/Cs_HMI/Data/NewMap.agvmap b/Cs_HMI/Data/NewMap.agvmap
index fa83ac4..2dc83fe 100644
--- a/Cs_HMI/Data/NewMap.agvmap
+++ b/Cs_HMI/Data/NewMap.agvmap
@@ -13,9 +13,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T08:34:44.9548285+09:00",
- "ModifiedDate": "2025-09-15T11:19:44.6879389+09:00",
+ "ModifiedDate": "2025-11-06T17:17:49.1294796+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Red",
"RfidId": "001",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -23,8 +23,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 10.0,
+ "TextFontBold": true,
+ "NameBubbleBackColor": "Orange",
+ "NameBubbleForeColor": "Black",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -36,7 +40,7 @@
{
"NodeId": "N002",
"Name": "N002",
- "Position": "206, 244",
+ "Position": "190, 230",
"Type": 0,
"DockDirection": 0,
"ConnectedNodes": [
@@ -47,9 +51,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T08:34:48.2957516+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "002",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -57,8 +61,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -70,7 +78,7 @@
{
"NodeId": "N003",
"Name": "N003",
- "Position": "278, 278",
+ "Position": "296, 266",
"Type": 0,
"DockDirection": 0,
"ConnectedNodes": [
@@ -81,9 +89,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T08:34:49.2226656+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "003",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -91,8 +99,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -104,7 +116,7 @@
{
"NodeId": "N004",
"Name": "N004",
- "Position": "380, 340",
+ "Position": "388, 330",
"Type": 0,
"DockDirection": 0,
"ConnectedNodes": [
@@ -117,9 +129,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T08:34:50.1681027+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "004",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -127,8 +139,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -140,7 +156,7 @@
{
"NodeId": "N006",
"Name": "N006",
- "Position": "520, 220",
+ "Position": "530, 220",
"Type": 0,
"DockDirection": 0,
"ConnectedNodes": [
@@ -151,9 +167,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T08:34:51.1111368+09:00",
- "ModifiedDate": "2025-09-15T10:16:24.8315093+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "013",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -161,8 +177,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -174,7 +194,7 @@
{
"NodeId": "N007",
"Name": "N007",
- "Position": "600, 180",
+ "Position": "589, 184",
"Type": 0,
"DockDirection": 0,
"ConnectedNodes": [
@@ -185,9 +205,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T08:34:51.9266982+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "014",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -195,8 +215,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -219,9 +243,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T08:34:53.9595825+09:00",
- "ModifiedDate": "2025-09-15T11:18:49.8874367+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "009",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -229,8 +253,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": true,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -253,9 +281,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T08:34:54.5035702+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "010",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -263,8 +291,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": true,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -286,9 +318,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T08:34:55.0563237+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:49.1294796+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Red",
"RfidId": "011",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -296,8 +328,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 10.0,
+ "TextFontBold": true,
+ "NameBubbleBackColor": "Orange",
+ "NameBubbleForeColor": "Black",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -309,7 +345,7 @@
{
"NodeId": "N011",
"Name": "N011",
- "Position": "478, 412",
+ "Position": "481, 399",
"Type": 0,
"DockDirection": 0,
"ConnectedNodes": [
@@ -321,9 +357,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T08:34:55.8875335+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "005",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -331,8 +367,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -355,9 +395,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T08:34:56.3678144+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "006",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -365,8 +405,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -389,9 +433,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T08:34:56.8390845+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "007",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -399,8 +443,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -422,9 +470,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T08:34:57.2549726+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:49.1294796+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Red",
"RfidId": "008",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -432,8 +480,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 10.0,
+ "TextFontBold": true,
+ "NameBubbleBackColor": "Orange",
+ "NameBubbleForeColor": "Black",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -455,9 +507,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T08:35:56.5359098+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:18:12.0236215+09:00",
"IsActive": true,
- "DisplayColor": "Red",
+ "DisplayColor": "Magenta",
"RfidId": "015",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -465,8 +517,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 10.0,
+ "TextFontBold": true,
+ "NameBubbleBackColor": "Orange",
+ "NameBubbleForeColor": "Black",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -478,7 +534,7 @@
{
"NodeId": "N022",
"Name": "N022",
- "Position": "459, 279",
+ "Position": "461, 267",
"Type": 0,
"DockDirection": 0,
"ConnectedNodes": [
@@ -490,9 +546,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T08:36:48.0311551+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "012",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -500,8 +556,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -513,7 +573,7 @@
{
"NodeId": "N023",
"Name": "N023",
- "Position": "434, 222",
+ "Position": "418, 206",
"Type": 0,
"DockDirection": 0,
"ConnectedNodes": [
@@ -524,9 +584,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T09:41:36.8738794+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "016",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -534,8 +594,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -547,7 +611,7 @@
{
"NodeId": "N024",
"Name": "N024",
- "Position": "485, 159",
+ "Position": "476, 141",
"Type": 0,
"DockDirection": 0,
"ConnectedNodes": [
@@ -558,9 +622,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T09:41:37.4551853+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "017",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -568,8 +632,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -581,7 +649,7 @@
{
"NodeId": "N025",
"Name": "N025",
- "Position": "556, 116",
+ "Position": "548, 99",
"Type": 0,
"DockDirection": 0,
"ConnectedNodes": [
@@ -592,9 +660,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T09:41:38.0142374+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "018",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -602,8 +670,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -625,9 +697,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-11T09:41:38.5834487+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:18:12.0236215+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Magenta",
"RfidId": "019",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -635,8 +707,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 10.0,
+ "TextFontBold": true,
+ "NameBubbleBackColor": "Orange",
+ "NameBubbleForeColor": "Black",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -648,7 +724,7 @@
{
"NodeId": "LBL001",
"Name": "Amkor Technology Korea",
- "Position": "102, 42",
+ "Position": "183, 103",
"Type": 4,
"DockDirection": 0,
"ConnectedNodes": [],
@@ -668,6 +744,10 @@
"FontStyle": 0,
"ForeColor": "White",
"BackColor": "DarkSlateGray",
+ "TextFontSize": 7.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": true,
"Padding": 5,
"ImageBase64": "",
@@ -679,7 +759,7 @@
{
"NodeId": "IMG001",
"Name": "logo",
- "Position": "671, 333",
+ "Position": "633, 310",
"Type": 5,
"DockDirection": 0,
"ConnectedNodes": [],
@@ -699,9 +779,13 @@
"FontStyle": 0,
"ForeColor": "Black",
"BackColor": "Transparent",
+ "TextFontSize": 7.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
- "ImageBase64": "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",
+ "ImageBase64": "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",
"Scale": "0.7, 0.7",
"Opacity": 1.0,
"Rotation": 0.0,
@@ -721,9 +805,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-12T17:22:47.8065756+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "037",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -731,8 +815,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -755,9 +843,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-12T17:22:48.6628848+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "036",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -765,8 +853,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -789,9 +881,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-12T17:22:49.8138877+09:00",
- "ModifiedDate": "2025-10-30T09:29:49.778537+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "035",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -799,8 +891,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -824,9 +920,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-12T17:22:50.6790623+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "034",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -834,8 +930,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -859,9 +959,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-12T17:22:51.5267199+09:00",
- "ModifiedDate": "2025-10-27T13:44:43.5601294+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "033",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -869,8 +969,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -894,9 +998,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-12T17:22:52.3666114+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "032",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -904,8 +1008,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -928,9 +1036,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-12T17:22:53.0958619+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:37.2685386+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "031",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -938,8 +1046,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": true,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -951,7 +1063,7 @@
{
"NodeId": "N027",
"Name": "BUF1",
- "Position": "65, 644",
+ "Position": "61, 645",
"Type": 2,
"DockDirection": 2,
"ConnectedNodes": [
@@ -961,7 +1073,7 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-12T17:22:54.7345704+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:10:24.191353+09:00",
"IsActive": true,
"DisplayColor": "Green",
"RfidId": "041",
@@ -971,8 +1083,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 10.0,
+ "TextFontBold": true,
+ "NameBubbleBackColor": "Orange",
+ "NameBubbleForeColor": "Black",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -984,7 +1100,7 @@
{
"NodeId": "N028",
"Name": "BUF2",
- "Position": "149, 645",
+ "Position": "141, 643",
"Type": 2,
"DockDirection": 2,
"ConnectedNodes": [
@@ -994,7 +1110,7 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-12T17:22:55.5263512+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:10:29.1638454+09:00",
"IsActive": true,
"DisplayColor": "Green",
"RfidId": "040",
@@ -1004,8 +1120,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 10.0,
+ "TextFontBold": true,
+ "NameBubbleBackColor": "Orange",
+ "NameBubbleForeColor": "Black",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -1017,7 +1137,7 @@
{
"NodeId": "N029",
"Name": "BUF3",
- "Position": "231, 639",
+ "Position": "229, 638",
"Type": 2,
"DockDirection": 2,
"ConnectedNodes": [
@@ -1027,7 +1147,7 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-12T17:22:56.6623294+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:10:35.6234001+09:00",
"IsActive": true,
"DisplayColor": "Green",
"RfidId": "039",
@@ -1037,8 +1157,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 10.0,
+ "TextFontBold": true,
+ "NameBubbleBackColor": "Orange",
+ "NameBubbleForeColor": "Black",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -1050,7 +1174,7 @@
{
"NodeId": "N030",
"Name": "BUF4",
- "Position": "314, 639",
+ "Position": "316, 638",
"Type": 2,
"DockDirection": 2,
"ConnectedNodes": [
@@ -1060,7 +1184,7 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-12T17:22:57.5510908+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:10:38.6071751+09:00",
"IsActive": true,
"DisplayColor": "Green",
"RfidId": "038",
@@ -1070,8 +1194,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 10.0,
+ "TextFontBold": true,
+ "NameBubbleBackColor": "Orange",
+ "NameBubbleForeColor": "Black",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -1094,9 +1222,9 @@
"StationId": "",
"StationType": null,
"CreatedDate": "2025-09-15T11:18:40.5366059+09:00",
- "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00",
+ "ModifiedDate": "2025-11-06T17:17:28.6401293+09:00",
"IsActive": true,
- "DisplayColor": "Blue",
+ "DisplayColor": "Cyan",
"RfidId": "030",
"RfidStatus": "정상",
"RfidDescription": "",
@@ -1104,8 +1232,12 @@
"FontFamily": "Arial",
"FontSize": 12.0,
"FontStyle": 0,
- "ForeColor": "Black",
+ "ForeColor": "White",
"BackColor": "Transparent",
+ "TextFontSize": 8.0,
+ "TextFontBold": false,
+ "NameBubbleBackColor": "",
+ "NameBubbleForeColor": "",
"ShowBackground": false,
"Padding": 0,
"ImageBase64": "",
@@ -1115,6 +1247,10 @@
"DisplayText": "N031 - [030]"
}
],
- "CreatedDate": "2025-11-04T10:42:50.5212661+09:00",
- "Version": "1.0"
+ "Settings": {
+ "BackgroundColorArgb": -14671840,
+ "ShowGrid": false
+ },
+ "CreatedDate": "2025-11-06T17:18:20.440108+09:00",
+ "Version": "1.1"
}
\ No newline at end of file
diff --git a/Cs_HMI/Project/AGV4.csproj b/Cs_HMI/Project/AGV4.csproj
index 190b825..85dfa17 100644
--- a/Cs_HMI/Project/AGV4.csproj
+++ b/Cs_HMI/Project/AGV4.csproj
@@ -523,6 +523,10 @@
+
+ {c5f7a8b2-8d3e-4a1b-9c6e-7f4d5e2a9b1c}
+ AGVNavigationCore
+
{bbc9bccf-6262-4355-9cc2-37ff678ac499}
StateMachine
diff --git a/Cs_HMI/Project/CSetting.cs b/Cs_HMI/Project/CSetting.cs
index 619f2a3..d993a5a 100644
--- a/Cs_HMI/Project/CSetting.cs
+++ b/Cs_HMI/Project/CSetting.cs
@@ -87,24 +87,26 @@ namespace Project
#region "Communication"
- [Category("Commnunication Setting"), DisplayName("XBee PortName"), Editor(typeof(MyUITypeEditor), typeof(UITypeEditor))]
+ [Browsable(false), Category("Commnunication Setting"), DisplayName("XBee PortName"), Editor(typeof(MyUITypeEditor), typeof(UITypeEditor))]
public string Port_XBE { get; set; }
- [Category("Commnunication Setting"), DisplayName("RFID PortName"), Editor(typeof(MyUITypeEditor), typeof(UITypeEditor))]
+ [Browsable(false), Category("Commnunication Setting"), DisplayName("RFID PortName"), Editor(typeof(MyUITypeEditor), typeof(UITypeEditor))]
public string Port_AGV { get; set; }
[Category("Commnunication Setting"), DisplayName("Xbee ID"), Editor(typeof(MyUITypeEditor), typeof(UITypeEditor))]
public byte XBE_ID { get; set; }
- [Category("Commnunication Setting"), DisplayName("BMS PortName"), Editor(typeof(MyUITypeEditor), typeof(UITypeEditor))]
+ [Browsable(false), Category("Commnunication Setting"), DisplayName("BMS PortName"), Editor(typeof(MyUITypeEditor), typeof(UITypeEditor))]
public string Port_BAT { get; set; }
public int ChargerID { get; set; }
+ [Browsable(false)]
public int Baud_AGV { get; set; }
+ [Browsable(false)]
public int Baud_BAT { get; set; }
- //public int Baud_PLC { get; set; }
+ [Browsable(false)]
public int Baud_XBE { get; set; }
//public int QueryInterval_BAT { get; set; }
@@ -219,6 +221,8 @@ namespace Project
#region "AGV"
public bool AutoModeOffAndClearPosition { get; set; }
+
+ [Browsable(false)]
public string musicfile { get; set; }
///
@@ -235,8 +239,11 @@ namespace Project
//public double ChargeLimitLow { get; set; }
//public double ChargeLimitHigh { get; set; }
+ [Browsable(false)]
public string AGV_PANID { get; set; }
+ [Browsable(false)]
public string AGV_CHANNEL { get; set; }
+ [Browsable(false)]
public string AGV_ADDRESS { get; set; }
public int SCK { get; set; }
@@ -269,31 +276,19 @@ namespace Project
public int PID_DS { get; set; }
public double WheelSpeedCharge { get; set; }
-
- //public byte MotorUpTime { get; set; }
- //public double AlignSensorThreshold { get; set; }
- // public byte BalanceThreshold { get; set; }
- // public byte BalanceThresholdUp { get; set; }
- // public byte MarkSensorThreshold { get; set; }
- //public Boolean Opt_LidarStop { get; set; }
- //public Boolean Opt_SlowUp { get; set; }
- //public Boolean Opt_Magnet { get; set; }
public byte HomePositionValue { get; set; }
public byte HomeKitNo { get; set; }
-
-
- //public Single interval_bms { get; set; }
public Single interval_xbe { get; set; }
public int interval_bms { get; set; }
- //public byte interval_iostate { get; set; }
- //public Boolean Enable_AutoZDnUp { get; set; }
public int doorSoundTerm { get; set; }
+
+ [Browsable(false)]
public int musicvol { get; set; }
public bool Enable_Music { get; set; }
#endregion
- [Category("Report"),
+ [Category("Report"), Browsable(false),
Description("상태기록시 장비 식별코드(4자리)"),
DisplayName("M/C ID")]
public string MCID { get; set; }
@@ -349,8 +344,8 @@ namespace Project
TAG_F3_F4 = 9650;
TAG_F4_F5 = 9750;
}
-
- if(TAG_F4_F5 == 0)
+
+ if (TAG_F4_F5 == 0)
{
TAG_F4_F5 = 9750;
TAGF5B = 9800;
@@ -368,10 +363,7 @@ namespace Project
if (ChargeStartLevel == 0) ChargeStartLevel = 85;
if (ChargeMaxLevel == 0) ChargeMaxLevel = 85;
if (ChargeEmergencyLevel == 0) ChargeEmergencyLevel = 30;
-
- if (interval_bms == 0) interval_bms = 1000;
- //충전은 대기상태 5분이 경과하면 진행한다
-
+ if (interval_bms == 0) interval_bms = 10;
//충전은 10분간격으로 재시도 한다
if (ChargeRetryTerm == 0) ChargeRetryTerm = 600;
@@ -379,7 +371,7 @@ namespace Project
if (ChargeSearchTime == 0) ChargeSearchTime = 25;
//최대 충전진행 시간(기본 1시간)
if (ChargeMaxTime == 0) ChargeMaxTime = 3600;
- // if (interval_iostate == 0 || interval_iostate == 255) interval_iostate = 100;
+ // if (interval_iostate == 0 || interval_iostate == 255) interval_iostate = 100;
if (ZSpeed == 0) ZSpeed = 20;
if (interval_xbe == 0) interval_xbe = 5.0f;
if (HomePositionValue == 0) HomePositionValue = 4;
@@ -419,7 +411,7 @@ namespace Project
if (string.IsNullOrEmpty(Port_AGV)) Port_AGV = "COM1";
- // if (string.IsNullOrEmpty(Port_PLC)) Port_PLC = "COM2";
+ // if (string.IsNullOrEmpty(Port_PLC)) Port_PLC = "COM2";
if (string.IsNullOrEmpty(Port_XBE)) Port_XBE = "COM4";
if (string.IsNullOrEmpty(Port_BAT)) Port_BAT = "COM7";
diff --git a/Cs_HMI/Project/Class/EEMStatus.cs b/Cs_HMI/Project/Class/EEMStatus.cs
index 61667e0..92157f3 100644
--- a/Cs_HMI/Project/Class/EEMStatus.cs
+++ b/Cs_HMI/Project/Class/EEMStatus.cs
@@ -99,6 +99,114 @@ public static partial class EEMStatus
queryok = true;
}
+ ///
+ /// 배터리데이터수신시 값을 기록한다.
+ ///
+ public static void MakeBMSInformation_INFO()
+ {
+ if (PUB.BMS.Current_DataTime.Year == 1982 || LastBMSIFTime == PUB.BMS.Current_DataTime || PUB.setting.MCID.isEmpty()) return;
+ try
+ {
+ // BMS 데이터 취득 (실제 BMS 객체에서 데이터를 가져와야 함)
+ // 예시: var bms = Project.Device.BMS.Instance;
+ var mcid = Project.PUB.setting.MCID;
+ var timestr = PUB.BMS.Current_DataTime.ToString("yyyy-MM-dd HH:mm:ss");
+
+ // BMS 데이터 (실제 값으로 교체 필요)
+ var info_volt = PUB.BMS.Current_Volt;// "null"; // bms.Voltage
+ var info_current = PUB.BMS.Current_Amp;// "null"; // bms.Current
+ var info_capa = PUB.BMS.Current_MaxAmp;// "null"; // bms.Capacity
+ var info_level = PUB.BMS.Current_Level;// "null"; // bms.Level
+ var info_temp1 = PUB.BMS.Current_temp1;// "null"; // bms.Temp1
+ var info_temp2 = PUB.BMS.Current_temp2;// "null"; // bms.Temp2
+ var cell_volt1 = PUB.BMS.CellVoltage[0];// "null"; // bms.CellVolt1
+ var cell_volt2 = PUB.BMS.CellVoltage[1]; // bms.CellVolt2
+ var cell_volt3 = PUB.BMS.CellVoltage[2]; // bms.CellVolt3
+ var cell_volt4 = PUB.BMS.CellVoltage[3]; // bms.CellVolt4
+ var cell_volt5 = PUB.BMS.CellVoltage[4]; // bms.CellVolt5
+ var cell_volt6 = PUB.BMS.CellVoltage[5]; // bms.CellVolt6
+ var cell_volt7 = PUB.BMS.CellVoltage[6]; // bms.CellVolt7
+ var cell_volt8 = PUB.BMS.CellVoltage[7]; // bms.CellVolt8
+
+ // Status 폴더에 SQL 파일 생성
+ var path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Status");
+ if (System.IO.Directory.Exists(path) == false)
+ System.IO.Directory.CreateDirectory(path);
+
+ var file = System.IO.Path.Combine(path, $"{DateTime.Now.ToString("HHmmssfff")}_BMS_INF.sql");
+
+ var sql = "insert into AGV_Shuttle_BMS(MCID,wdate,info_volt,info_current,info_capa,info_level,info_temp1,info_temp2) " +
+ "values('{0}','{1}',{2},{3},{4},{5},{6},{7})";
+
+ sql = string.Format(sql, mcid, timestr, info_volt, info_current, info_capa, info_level, info_temp1, info_temp2);
+
+ System.IO.File.WriteAllText(file, sql, System.Text.Encoding.Default);
+ LastBMSIFTime = DateTime.Now;
+ }
+ catch (Exception ex)
+ {
+ // 오류 로깅 (필요시 PUB.log.Add 등으로 처리)
+ Console.WriteLine($"MakeBMSInformation Error: {ex.Message}");
+ }
+ }
+
+ static DateTime LastBMSIFTime = DateTime.Now;
+ static DateTime LastBMSCVTime = DateTime.Now;
+
+ ///
+ /// 배터리데이터수신시 값을 기록한다.
+ ///
+ public static void MakeBMSInformation_Cell()
+ {
+ if (PUB.BMS.Current_CellTime.Year == 1982 || LastBMSCVTime == PUB.BMS.Current_CellTime || PUB.setting.MCID.isEmpty()) return;
+ try
+ {
+ // BMS 데이터 취득 (실제 BMS 객체에서 데이터를 가져와야 함)
+ // 예시: var bms = Project.Device.BMS.Instance;
+ var mcid = Project.PUB.setting.MCID;
+ var timestr = PUB.BMS.Current_CellTime.ToString("yyyy-MM-dd HH:mm:ss");
+
+ // BMS 데이터 (실제 값으로 교체 필요)
+ var info_volt = PUB.BMS.Current_Volt;// "null"; // bms.Voltage
+ var info_current = PUB.BMS.Current_Amp;// "null"; // bms.Current
+ var info_capa = PUB.BMS.Current_MaxAmp;// "null"; // bms.Capacity
+ var info_level = PUB.BMS.Current_Level;// "null"; // bms.Level
+ var info_temp1 = PUB.BMS.Current_temp1;// "null"; // bms.Temp1
+ var info_temp2 = PUB.BMS.Current_temp2;// "null"; // bms.Temp2
+ var cell_volt1 = PUB.BMS.CellVoltage[0];// "null"; // bms.CellVolt1
+ var cell_volt2 = PUB.BMS.CellVoltage[1]; // bms.CellVolt2
+ var cell_volt3 = PUB.BMS.CellVoltage[2]; // bms.CellVolt3
+ var cell_volt4 = PUB.BMS.CellVoltage[3]; // bms.CellVolt4
+ var cell_volt5 = PUB.BMS.CellVoltage[4]; // bms.CellVolt5
+ var cell_volt6 = PUB.BMS.CellVoltage[5]; // bms.CellVolt6
+ var cell_volt7 = PUB.BMS.CellVoltage[6]; // bms.CellVolt7
+ var cell_volt8 = PUB.BMS.CellVoltage[7]; // bms.CellVolt8
+
+ // Status 폴더에 SQL 파일 생성
+ var path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Status");
+ if (System.IO.Directory.Exists(path) == false)
+ System.IO.Directory.CreateDirectory(path);
+
+ var file = System.IO.Path.Combine(path, $"{DateTime.Now.ToString("HHmmssfff")}_BMS_CV.sql");
+
+ var sql = "insert into AGV_Shuttle_BMS(MCID,wdate," +
+ "cell_volt1,cell_volt2,cell_volt3,cell_volt4,cell_volt5,cell_volt6,cell_volt7,cell_volt8) " +
+ "values('{0}','{1}',{2},{3},{4},{5},{6},{7},{8},{9})";
+
+ sql = string.Format(sql, mcid, timestr,
+ cell_volt1, cell_volt2, cell_volt3, cell_volt4,
+ cell_volt5, cell_volt6, cell_volt7, cell_volt8);
+
+ System.IO.File.WriteAllText(file, sql, System.Text.Encoding.Default);
+ LastBMSCVTime = DateTime.Now;
+ }
+ catch (Exception ex)
+ {
+ // 오류 로깅 (필요시 PUB.log.Add 등으로 처리)
+ Console.WriteLine($"MakeBMSInformation Error: {ex.Message}");
+ }
+ }
+
public static void UpdateStatusSQL(eSMStep status, bool _extrun = false, string remark = "")
{
var tsrun = DateTime.Now - StatusChecktime;
diff --git a/Cs_HMI/Project/PUB.cs b/Cs_HMI/Project/PUB.cs
index e90efce..df198a3 100644
--- a/Cs_HMI/Project/PUB.cs
+++ b/Cs_HMI/Project/PUB.cs
@@ -12,6 +12,10 @@ using Microsoft.Speech.Synthesis;
using System.Threading.Tasks;
using System.Data.SqlClient;
using System.Linq;
+using AGVNavigationCore.Models;
+using AGVNavigationCore.Controls;
+using System.Collections.Generic;
+using System.Drawing;
namespace Project
{
@@ -22,7 +26,13 @@ namespace Project
public static bool Automodeonreboot = false;
public static bool AutRebootAlreay = false;
public static bool DriveSpeed = false;
- public static AGVControl.MapControl mapctl;
+ public static AGVNavigationCore.Controls.UnifiedAGVCanvas _mapCanvas;
+ public static List _mapNodes;
+
+ ///
+ /// 가상 AGV (시뮬레이션용)
+ ///
+ public static VirtualAGV _virtualAGV;
#region "Hardware"
@@ -570,5 +580,140 @@ namespace Project
}
+ #region VirtualAGV 실제 데이터 동기화
+
+ ///
+ /// RFID 읽기 시 해당 노드 위치로 AGV 업데이트
+ ///
+ /// 읽은 RFID ID
+ /// 모터 방향 (Forward/Backward)
+ /// 업데이트 성공 여부
+ public static bool UpdateAGVFromRFID(string rfidId, AgvDirection motorDirection = AgvDirection.Forward)
+ {
+ if (_virtualAGV == null || _mapNodes == null) return false;
+
+ // RFID에 해당하는 노드 찾기
+ var node = _mapNodes.FirstOrDefault(n => n.RfidId == rfidId);
+ if (node != null)
+ {
+ _virtualAGV.SetPosition(node, motorDirection);
+ RefreshAGVCanvas();
+
+ log.Add($"[AGV] RFID {rfidId} 감지 → 노드 {node.NodeId} 위치 업데이트 (방향: {motorDirection})");
+ return true;
+ }
+
+ log.Add($"[AGV] RFID {rfidId}에 해당하는 노드를 찾을 수 없음");
+ return false;
+ }
+
+ ///
+ /// 노드ID로 AGV 위치 업데이트
+ ///
+ /// 노드 ID
+ /// 모터 방향 (Forward/Backward)
+ /// 업데이트 성공 여부
+ public static bool UpdateAGVToNode(string nodeId, AgvDirection motorDirection = AgvDirection.Forward)
+ {
+ if (_virtualAGV == null || _mapNodes == null) return false;
+
+ var node = _mapNodes.FirstOrDefault(n => n.NodeId == nodeId);
+ if (node != null)
+ {
+ _virtualAGV.SetPosition(node, motorDirection);
+ RefreshAGVCanvas();
+
+ log.Add($"[AGV] 노드 {nodeId} 위치로 이동 (방향: {motorDirection})");
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// AGV 방향 업데이트
+ ///
+ /// 새로운 방향
+ public static void UpdateAGVDirection(AgvDirection direction)
+ {
+ if (_virtualAGV == null) return;
+
+ _virtualAGV.CurrentDirection = direction;
+ RefreshAGVCanvas();
+ }
+
+ ///
+ /// AGV 상태 업데이트
+ ///
+ /// 새로운 상태
+ public static void UpdateAGVState(AGVState state)
+ {
+ if (_virtualAGV == null) return;
+
+ _virtualAGV.CurrentState = state;
+ RefreshAGVCanvas();
+ }
+
+ ///
+ /// AGV 배터리 레벨 업데이트
+ ///
+ /// 배터리 레벨 (0.0 ~ 100.0)
+ public static void UpdateAGVBattery(float batteryLevel)
+ {
+ if (_virtualAGV == null) return;
+
+ _virtualAGV.BatteryLevel = batteryLevel;
+ RefreshAGVCanvas();
+ }
+
+ ///
+ /// 맵 캔버스 강제 갱신 (AGV 위치 표시 업데이트)
+ ///
+ public static void RefreshAGVCanvas()
+ {
+ if (_mapCanvas != null && _mapCanvas.IsHandleCreated)
+ {
+ _mapCanvas.Invalidate();
+ }
+ }
+
+ ///
+ /// 현재 AGV의 노드 ID 가져오기
+ ///
+ /// 현재 노드 ID
+ public static string GetCurrentAGVNodeId()
+ {
+ return _virtualAGV?.CurrentNodeId ?? string.Empty;
+ }
+
+ ///
+ /// 현재 AGV 위치 가져오기
+ ///
+ /// 현재 위치
+ public static Point GetCurrentAGVPosition()
+ {
+ return _virtualAGV?.CurrentPosition ?? Point.Empty;
+ }
+
+ ///
+ /// 현재 AGV 방향 가져오기
+ ///
+ /// 현재 방향
+ public static AgvDirection GetCurrentAGVDirection()
+ {
+ return _virtualAGV?.CurrentDirection ?? AgvDirection.Forward;
+ }
+
+ ///
+ /// 현재 AGV 상태 가져오기
+ ///
+ /// 현재 상태
+ public static AGVState GetCurrentAGVState()
+ {
+ return _virtualAGV?.CurrentState ?? AGVState.Idle;
+ }
+
+ #endregion
+
}
}
diff --git a/Cs_HMI/Project/Properties/AssemblyInfo.cs b/Cs_HMI/Project/Properties/AssemblyInfo.cs
index 3b9c3ae..14e75dc 100644
--- a/Cs_HMI/Project/Properties/AssemblyInfo.cs
+++ b/Cs_HMI/Project/Properties/AssemblyInfo.cs
@@ -5,11 +5,11 @@ using System.Runtime.InteropServices;
// 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해
// 제어됩니다. 어셈블리와 관련된 정보를 수정하려면
// 이러한 특성 값을 변경하세요.
-[assembly: AssemblyTitle("(OTP) 2D Reading System")]
+[assembly: AssemblyTitle("ENIG Shuttle Narmi")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Amkor K4")]
-[assembly: AssemblyProduct("(OTP) 2D Reading System")]
+[assembly: AssemblyProduct("ENIG Shuttle Narmi")]
[assembly: AssemblyCopyright("Copyright ©Amkor-EET 2020")]
[assembly: AssemblyTrademark("EET")]
[assembly: AssemblyCulture("")]
diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOTO.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOTO.cs
index f48a3a8..423d9af 100644
--- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOTO.cs
+++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOTO.cs
@@ -30,259 +30,259 @@ namespace Project
}
//목적지가 설정되었는지 체크한다.
- 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;
- }
+ //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;
+ //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;
+ // //상태값이 바뀌었다면 전송을 해야한다
+ // 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.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;
+ // 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,
- });
+ // //이동셋팅을 해준다
+ // 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 (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");
+ // //정지상태라면 이동 명령을 전달한다
+ // 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;
+ // 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;
- }
- }
+ // 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);
- }
- }
+ // //예측이 업데이트되지 않으면 오류 처리해야한다
+ // 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;
+ // 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);
- }
+ // //최대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 (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;
- }
+ // //방향전환용 턴이라면 이동기록을 추가해서 방향이 맞도록 처리해주자
+ // 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;
- }
+ // PUB.sm.UpdateRunStepSeq();
+ // }
+ // else PUB.sm.UpdateRunStepSeq();
+ // return false;
+ //}
//좌턴이동명령 전송
diff --git a/Cs_HMI/Project/StateMachine/_AGV.cs b/Cs_HMI/Project/StateMachine/_AGV.cs
index 53786c7..f93f65d 100644
--- a/Cs_HMI/Project/StateMachine/_AGV.cs
+++ b/Cs_HMI/Project/StateMachine/_AGV.cs
@@ -56,33 +56,33 @@ namespace Project
VAR.BOOL[eVarBool.AGV_ERROR] = PUB.AGV.error.Value > 0;
VAR.BOOL[eVarBool.EMERGENCY] = PUB.AGV.error.Emergency;
- //모터방향 입력
- if (PUB.AGV.data.Direction == 'B')
- PUB.mapctl.Manager.agv.Current_Motor_Direction = AGVControl.AgvDir.Backward;
- else
- PUB.mapctl.Manager.agv.Current_Motor_Direction = AGVControl.AgvDir.Forward;
+ ////모터방향 입력
+ //if (PUB.AGV.data.Direction == 'B')
+ // PUB.mapctl.Manager.agv.Current_Motor_Direction = AGVControl.AgvDir.Backward;
+ //else
+ // PUB.mapctl.Manager.agv.Current_Motor_Direction = AGVControl.AgvDir.Forward;
- //현재 속도
- if (PUB.AGV.data.Speed == 'H')
- PUB.mapctl.Manager.agv.CurrentSpeed = AGVControl.AgvSpeed.High;
- else if (PUB.AGV.data.Speed == 'M')
- PUB.mapctl.Manager.agv.CurrentSpeed = AGVControl.AgvSpeed.Middle;
- else if (PUB.AGV.data.Speed == 'L')
- PUB.mapctl.Manager.agv.CurrentSpeed = AGVControl.AgvSpeed.Low;
- else if (PUB.AGV.data.Speed == 'S')
- PUB.mapctl.Manager.agv.CurrentSpeed = AGVControl.AgvSpeed.MarkStop;
+ ////현재 속도
+ //if (PUB.AGV.data.Speed == 'H')
+ // PUB.mapctl.Manager.agv.CurrentSpeed = AGVControl.AgvSpeed.High;
+ //else if (PUB.AGV.data.Speed == 'M')
+ // PUB.mapctl.Manager.agv.CurrentSpeed = AGVControl.AgvSpeed.Middle;
+ //else if (PUB.AGV.data.Speed == 'L')
+ // PUB.mapctl.Manager.agv.CurrentSpeed = AGVControl.AgvSpeed.Low;
+ //else if (PUB.AGV.data.Speed == 'S')
+ // PUB.mapctl.Manager.agv.CurrentSpeed = AGVControl.AgvSpeed.MarkStop;
- //이동방향
- if (PUB.AGV.data.Sts == 'S')
- PUB.mapctl.Manager.agv.CurrentSTS = AGVControl.AgvSts.Straight;
- else if (PUB.AGV.data.Sts == 'L')
- PUB.mapctl.Manager.agv.CurrentSTS = AGVControl.AgvSts.Left;
- else if (PUB.AGV.data.Sts == 'R')
- PUB.mapctl.Manager.agv.CurrentSTS = AGVControl.AgvSts.Right;
+ ////이동방향
+ //if (PUB.AGV.data.Sts == 'S')
+ // PUB.mapctl.Manager.agv.CurrentSTS = AGVControl.AgvSts.Straight;
+ //else if (PUB.AGV.data.Sts == 'L')
+ // PUB.mapctl.Manager.agv.CurrentSTS = AGVControl.AgvSts.Left;
+ //else if (PUB.AGV.data.Sts == 'R')
+ // PUB.mapctl.Manager.agv.CurrentSTS = AGVControl.AgvSts.Right;
- PUB.mapctl.Manager.agv.IsMoving = PUB.AGV.system1.agv_run;
- PUB.mapctl.Manager.agv.IsMarkCheck = PUB.AGV.system1.Mark1_check || PUB.AGV.system1.Mark2_check;
+ //PUB.mapctl.Manager.agv.IsMoving = PUB.AGV.system1.agv_run;
+ //PUB.mapctl.Manager.agv.IsMarkCheck = PUB.AGV.system1.Mark1_check || PUB.AGV.system1.Mark2_check;
if (PUB.AGV.signal.mark_sensor == false)
{
@@ -184,41 +184,18 @@ namespace Project
PUB.log.AddE(logEMsg);
}
- //맵데이터에서 현재 위치를 찾는다
- if (PUB.mapctl.SetCurrentPosition(PUB.AGV.data.TagNo) == false)
+ //virtual agv setting
+ var CurrentNode = PUB._mapNodes.FirstOrDefault(t => t.RfidId.Equals(PUB.Result.LastTAG, StringComparison.OrdinalIgnoreCase));
+ if (CurrentNode == null)
{
- if (VAR.BOOL[eVarBool.FLAG_AUTORUN] && PUB.AGV.system1.agv_run)
- PUB.AGV.AGVMoveStop("unknown tag no");
-
- //존재하지 않는 태그가 읽히면 관련 오류를 표시한다.
- }
- else
- {
- //위치는 찾았다 해당 위치가 내 목적지라면 mark stop기능으로 전환한다
+ PUB.log.AddE($"RFID:{PUB.Result.LastTAG} 의 노드를 찾을 수 없습니다");
+ return;
}
- ////자동, 상하차 모드일때 RFID 가 타겟위치에 올때는 - 멈춤을 설정해준다
- //if (VAR.BOOL[eVarBool.FLAG_AUTORUN] == true &&
- // PUB.Result.CurrentPos == PUB.Result.TargetPos &&
- // PUB.Result.TargetPos != ePosition.NONE &&
- // (PUB.sm.RunStep == ERunStep.GODOWN ||
- // PUB.sm.RunStep == ERunStep.GOUP ||
- // PUB.sm.RunStep == ERunStep.GOHOME ||
- // PUB.sm.RunStep == ERunStep.GOCHARGE))
- //{
- // if (PUB.AGV.data.Sts == 'F' && dirForward == "0") //아래로 내려오고있음
- // {
- // PUB.AGV.AGVMoveStop("AGV_DataReceive", arDev.Narumi.eStopOpt.MarkStop);
- // PUB.Speak( Lang.다음마크위치에서정지합니다);
- // }
- // else if (PUB.AGV.data.Sts == 'B' && dirForward == "1")
- // {
- // //VAR.BOOL[eVarBool.FLAG_NEXTSTOP_MARK] = true;
- // PUB.AGV.AGVMoveStop("AGV_DataReceive", arDev.Narumi.eStopOpt.MarkStop);
- // PUB.Speak(Lang.다음마크위치에서정지합니다);
- // }
- //}
-
+ //모터방향 확인해서 UI와 AGV클래스에 적용한다
+ var MotDireciton = PUB.AGV.data.Direction == 'B' ? AGVNavigationCore.Models.AgvDirection.Backward : AGVNavigationCore.Models.AgvDirection.Forward;
+ PUB._virtualAGV.SetPosition(CurrentNode, MotDireciton);
+ PUB._mapCanvas.SetAGVPosition("AGV", CurrentNode, MotDireciton);
}
break;
case arDev.Narumi.DataType.ACK:
@@ -231,12 +208,8 @@ namespace Project
}
//이 후 상황을 예측한다
- if (PUB.mapctl != null)
- {
- var rlt = PUB.mapctl.Manager.PredictNextAction();
- if (rlt.Changed)
- Console.WriteLine($"[new] predict idx:{rlt.Idx}");
- }
+ var command = PUB._virtualAGV.Predict();
+ var preditMSG = $"Motor:{command.Motor},Magnet:{command.Magnet},Speed:{command.Speed} : {command.Reason}";
}
catch (Exception ex)
{
diff --git a/Cs_HMI/Project/StateMachine/_BMS.cs b/Cs_HMI/Project/StateMachine/_BMS.cs
index c11a53a..9861fe4 100644
--- a/Cs_HMI/Project/StateMachine/_BMS.cs
+++ b/Cs_HMI/Project/StateMachine/_BMS.cs
@@ -25,7 +25,7 @@ namespace Project
DateTime lastbmstime = DateTime.Now;
private void Bms_Message(object sender, arDev.BMS.MessageEventArgs e)
{
- if (e.MsgType == arRS232.MessageType.Error) PUB.logbms.AddE( e.Message);
+ if (e.MsgType == arDev.arRS232.MessageType.Error) PUB.logbms.AddE( e.Message);
else
{
var hexstr = e.Data.GetHexString().Trim();
@@ -159,9 +159,9 @@ namespace Project
private void Bms_BMSDataReceive(object sender, EventArgs e)
{
- 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;
+ //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;
if (PUB.BMS.Current_Level <= PUB.setting.ChargeStartLevel)
{
//배터리 레벨이 기준보다 낮다면 경고를 활성화 한다
@@ -180,6 +180,11 @@ namespace Project
PUB.log.AddAT("배터리 부족 경고 비활성화");
}
}
+ EEMStatus.MakeBMSInformation_INFO();
+ }
+ private void BMS_BMSCellDataReceive(object sender, arDev.BMSCelvoltageEventArgs e)
+ {
+ EEMStatus.MakeBMSInformation_Cell();
}
}
}
diff --git a/Cs_HMI/Project/StateMachine/_SPS.cs b/Cs_HMI/Project/StateMachine/_SPS.cs
index 670c77e..1f68f56 100644
--- a/Cs_HMI/Project/StateMachine/_SPS.cs
+++ b/Cs_HMI/Project/StateMachine/_SPS.cs
@@ -117,10 +117,6 @@ namespace Project
Console.WriteLine($"bms connect to {PUB.setting.Port_BAT}");
PUB.BMS.PortName = PUB.setting.Port_BAT;
PUB.BMS.Open();
-
- PUB.BMS.BMSDataReceive += Bms_BMSDataReceive;
- PUB.BMS.Message += Bms_Message;
- PUB.BMS.ChargeDetect += BMS_ChargeDetect;
VAR.TIME.Update(eVarTime.LastConn_BAT);
VAR.TIME.Update(eVarTime.LastConnTry_BAT);
@@ -132,9 +128,6 @@ namespace Project
if (ts.TotalSeconds > 10)
{
Console.WriteLine("bms auto disconnect");
- PUB.BMS.BMSDataReceive -= Bms_BMSDataReceive;
- PUB.BMS.Message -= Bms_Message;
- PUB.BMS.ChargeDetect -= BMS_ChargeDetect;
PUB.BMS.Close();
VAR.TIME.Set(eVarTime.LastConn_BAT,DateTime.Now.AddSeconds(5));
}
@@ -159,7 +152,7 @@ namespace Project
{
if (PUB.BMS.lastSendTime.Year == 1982) PUB.BMS.lastSendTime = DateTime.Now.AddSeconds(1);
var ts = DateTime.Now - PUB.BMS.lastSendTime;
- if (ts.TotalMilliseconds >= PUB.setting.interval_bms)
+ if (ts.TotalSeconds >= PUB.setting.interval_bms)
{
PUB.BMS.SendQuery();
}
diff --git a/Cs_HMI/Project/StateMachine/_TMDisplay.cs b/Cs_HMI/Project/StateMachine/_TMDisplay.cs
index f9df0c9..9a7dfec 100644
--- a/Cs_HMI/Project/StateMachine/_TMDisplay.cs
+++ b/Cs_HMI/Project/StateMachine/_TMDisplay.cs
@@ -65,9 +65,6 @@ namespace Project
lbBat.CurA = PUB.BMS.Current_Amp;
lbBat.IsOpen = PUB.BMS.IsOpen;
- if (PUB.mapctl != null)
- PUB.mapctl.Invalidate();
-
//쓰레드로인해서 메인에서 진행하게한다. SPS는 메인쓰레드에서 진행 됨
//팝을 제거 혹은 표시하는 기능
if (PUB.popup.needShow) PUB.popup.showMessage();
diff --git a/Cs_HMI/Project/StateMachine/_Xbee.cs b/Cs_HMI/Project/StateMachine/_Xbee.cs
index c26fb92..8e33027 100644
--- a/Cs_HMI/Project/StateMachine/_Xbee.cs
+++ b/Cs_HMI/Project/StateMachine/_Xbee.cs
@@ -60,11 +60,11 @@ namespace Project
{
if (ushort.TryParse(targstr, out ushort tagno))
{
- if (PUB.mapctl.SetCurrentPosition(tagno) == true)
- {
- PUB.log.AddI($"Set Position:{tagno}");
- }
- else PUB.log.AddE($"Position Set Error:{tagno}");
+ //if (PUB.mapctl.SetCurrentPosition(tagno) == true)
+ //{
+ // PUB.log.AddI($"Set Position:{tagno}");
+ //}
+ //else PUB.log.AddE($"Position Set Error:{tagno}");
}
else PUB.log.AddE($"Position Param(tagstr) Error:{dataStr}");
}
@@ -79,11 +79,11 @@ namespace Project
case ENIGProtocol.AGVCommandHE.Goto: //move to tag
if (uint.TryParse(dataStr, out uint tagno2))
{
- var currPos = PUB.mapctl.Manager.agv.CurrentRFID;///.AGVMoveToRFID(;
- if (PUB.mapctl.SetTargetPosition(tagno2))
- PUB.log.AddI($"New Target {tagno2}");
- else
- PUB.log.AddE($"Path Error {tagno2}");
+ //var currPos = PUB.mapctl.Manager.agv.CurrentRFID;///.AGVMoveToRFID(;
+ //if (PUB.mapctl.SetTargetPosition(tagno2))
+ // PUB.log.AddI($"New Target {tagno2}");
+ //else
+ // PUB.log.AddE($"Path Error {tagno2}");
}
else PUB.log.AddE($"Path Param Error :{dataStr}");
break;
diff --git a/Cs_HMI/Project/ViewForm/fAuto.cs b/Cs_HMI/Project/ViewForm/fAuto.cs
index 843dcb8..e430a6a 100644
--- a/Cs_HMI/Project/ViewForm/fAuto.cs
+++ b/Cs_HMI/Project/ViewForm/fAuto.cs
@@ -10,12 +10,13 @@ using System.Windows.Forms;
using Project.StateMachine;
using COMM;
using AR;
+using AGVNavigationCore.Models;
namespace Project.ViewForm
{
public partial class fAuto : Form
{
-
+
public fAuto()
{
InitializeComponent();
@@ -26,46 +27,143 @@ namespace Project.ViewForm
this.ctlAuto1.Scean = CtlAuto.eScean.Progress;
else
this.ctlAuto1.Scean = CtlAuto.eScean.Normal;
- 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);
+
+ 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;
+
+ // 스플리터 패널에 맵 캔버스 추가
+ panel1.Controls.Add(PUB._mapCanvas);
+
+ // 툴바 버튼 이벤트 연결
+ //WireToolbarButtonEvents();
+
+
+
+ }
+
+
private void fAuto_Load(object sender, EventArgs e)
{
ctlAuto1.dev_agv = PUB.AGV;
// ctlAuto1.dev_plc = PUB.PLC;
ctlAuto1.dev_bms = PUB.BMS;
ctlAuto1.dev_xbe = PUB.XBE;
-
+
PUB.AGV.DataReceive += AGV_DataReceive;
-
-
+
+
//auto load
var path = new System.IO.DirectoryInfo("route");
if (path.Exists == false) path.Create();
var files = path.GetFiles("*.route");
- var fn = string.Empty;
- if (files.Any() == false)
+
+
+ //맵파일로딩
+ var filePath = new System.IO.FileInfo(@".\route\NewMap.agvmap");
+ if (filePath.Exists)
{
- fn = AR.UTIL.MakePath("sample.route");
- }
- else if (files.Count() == 1)
- {
- fn = files.First().FullName;
- }
- if(fn.isEmpty()==false)
- {
- var fi = new System.IO.FileInfo(AR.UTIL.CurrentPath + "\\sample.route");
- if (fi.Exists)
+ var result = MapLoader.LoadMapFromFile(filePath.FullName);
+
+ if (result.Success)
{
- PUB.log.Add($"autoload : {fi.FullName}");
- var rlt = PUB.mapctl.LoadFromFile(fi.FullName,out string errmsg);
- if (rlt == false) AR.UTIL.MsgE(errmsg);
+ if (PUB._mapNodes == null) PUB._mapNodes = new List();
+ else PUB._mapNodes.Clear();
+ PUB._mapNodes.AddRange(result.Nodes);
+
+ // 맵 캔버스에 데이터 설정
+ PUB._mapCanvas.Nodes = PUB._mapNodes;
+
+ // 🔥 맵 설정 적용 (배경색, 그리드 표시)
+ 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("AGV-01", startNode.Position, AgvDirection.Forward);
+ PUB._virtualAGV.SetPosition(startNode, AgvDirection.Forward);
+
+ // 캔버스에 AGV 리스트 설정
+ var agvList = new System.Collections.Generic.List { 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 { 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)
+ //{
+ // fn = AR.UTIL.MakePath("sample.route");
+ //}
+ //else if (files.Count() == 1)
+ //{
+ // fn = files.First().FullName;
+ //}
+ //if (fn.isEmpty() == false)
+ //{
+ // var fi = new System.IO.FileInfo(AR.UTIL.CurrentPath + "\\sample.route");
+ // if (fi.Exists)
+ // {
+ // PUB.log.Add($"autoload : {fi.FullName}");
+ // var rlt = PUB.mapctl.LoadFromFile(fi.FullName, out string errmsg);
+ // if (rlt == false) AR.UTIL.MsgE(errmsg);
+ // }
+ //}
this.timer1.Start();
}
@@ -107,7 +205,7 @@ namespace Project.ViewForm
}
bool tmrun = false;
-
+
private void fAuto_VisibleChanged(object sender, EventArgs e)
{
this.timer1.Enabled = this.Visible;
diff --git a/Cs_HMI/Project/fMain.Designer.cs b/Cs_HMI/Project/fMain.Designer.cs
index 16978a8..043b077 100644
--- a/Cs_HMI/Project/fMain.Designer.cs
+++ b/Cs_HMI/Project/fMain.Designer.cs
@@ -294,7 +294,7 @@ namespace Project
this.btAutoRun.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.ForwardDiagonal;
this.btAutoRun.GradientRepeatBG = false;
this.btAutoRun.isButton = true;
- this.btAutoRun.Location = new System.Drawing.Point(0, 274);
+ this.btAutoRun.Location = new System.Drawing.Point(0, 288);
this.btAutoRun.Margin = new System.Windows.Forms.Padding(0);
this.btAutoRun.MouseDownColor = System.Drawing.Color.Empty;
this.btAutoRun.MouseOverColor = System.Drawing.Color.Empty;
@@ -315,7 +315,7 @@ namespace Project
this.btAutoRun.SignAlign = System.Drawing.ContentAlignment.BottomRight;
this.btAutoRun.SignColor = System.Drawing.Color.Yellow;
this.btAutoRun.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
- this.btAutoRun.Size = new System.Drawing.Size(259, 139);
+ this.btAutoRun.Size = new System.Drawing.Size(259, 145);
this.btAutoRun.TabIndex = 22;
this.btAutoRun.Text = "수동";
this.btAutoRun.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
@@ -362,7 +362,7 @@ namespace Project
this.btChargeA.SignAlign = System.Drawing.ContentAlignment.BottomRight;
this.btChargeA.SignColor = System.Drawing.Color.Yellow;
this.btChargeA.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
- this.btChargeA.Size = new System.Drawing.Size(143, 137);
+ this.btChargeA.Size = new System.Drawing.Size(143, 144);
this.btChargeA.TabIndex = 141;
this.btChargeA.Text = "자동충전";
this.btChargeA.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
@@ -388,7 +388,7 @@ namespace Project
this.lbTime.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.Vertical;
this.lbTime.GradientRepeatBG = false;
this.lbTime.isButton = false;
- this.lbTime.Location = new System.Drawing.Point(5, 554);
+ this.lbTime.Location = new System.Drawing.Point(5, 574);
this.lbTime.Margin = new System.Windows.Forms.Padding(0);
this.lbTime.MouseDownColor = System.Drawing.Color.Yellow;
this.lbTime.MouseOverColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
@@ -416,7 +416,6 @@ namespace Project
this.lbTime.TextShadow = false;
this.lbTime.TextVisible = true;
this.toolTip1.SetToolTip(this.lbTime, "현재 시간");
- this.lbTime.Click += new System.EventHandler(this.lbTime_Click);
//
// btDebug
//
@@ -978,7 +977,7 @@ namespace Project
this.panRight.Location = new System.Drawing.Point(1015, 146);
this.panRight.Name = "panRight";
this.panRight.Padding = new System.Windows.Forms.Padding(5, 0, 0, 0);
- this.panRight.Size = new System.Drawing.Size(264, 579);
+ this.panRight.Size = new System.Drawing.Size(264, 599);
this.panRight.TabIndex = 131;
//
// tableLayoutPanel1
@@ -997,7 +996,7 @@ namespace Project
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F));
- this.tableLayoutPanel1.Size = new System.Drawing.Size(259, 413);
+ this.tableLayoutPanel1.Size = new System.Drawing.Size(259, 433);
this.tableLayoutPanel1.TabIndex = 0;
//
// btHome
@@ -1039,7 +1038,7 @@ namespace Project
this.btHome.SignAlign = System.Drawing.ContentAlignment.BottomRight;
this.btHome.SignColor = System.Drawing.Color.Yellow;
this.btHome.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
- this.btHome.Size = new System.Drawing.Size(116, 274);
+ this.btHome.Size = new System.Drawing.Size(116, 288);
this.btHome.TabIndex = 141;
this.btHome.Text = "홈";
this.btHome.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
@@ -1064,7 +1063,7 @@ namespace Project
this.btChargeM.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.ForwardDiagonal;
this.btChargeM.GradientRepeatBG = false;
this.btChargeM.isButton = true;
- this.btChargeM.Location = new System.Drawing.Point(116, 137);
+ this.btChargeM.Location = new System.Drawing.Point(116, 144);
this.btChargeM.Margin = new System.Windows.Forms.Padding(0);
this.btChargeM.MouseDownColor = System.Drawing.Color.Yellow;
this.btChargeM.MouseOverColor = System.Drawing.Color.Lime;
@@ -1085,7 +1084,7 @@ namespace Project
this.btChargeM.SignAlign = System.Drawing.ContentAlignment.BottomRight;
this.btChargeM.SignColor = System.Drawing.Color.Yellow;
this.btChargeM.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
- this.btChargeM.Size = new System.Drawing.Size(143, 137);
+ this.btChargeM.Size = new System.Drawing.Size(143, 144);
this.btChargeM.TabIndex = 141;
this.btChargeM.Text = "수동충전";
this.btChargeM.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
@@ -1494,7 +1493,7 @@ namespace Project
//
this.panel5.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(18)))), ((int)(((byte)(18)))), ((int)(((byte)(18)))));
this.panel5.Dock = System.Windows.Forms.DockStyle.Bottom;
- this.panel5.Location = new System.Drawing.Point(5, 549);
+ this.panel5.Location = new System.Drawing.Point(5, 569);
this.panel5.Name = "panel5";
this.panel5.Size = new System.Drawing.Size(259, 5);
this.panel5.TabIndex = 142;
@@ -1716,10 +1715,10 @@ namespace Project
//
this.pandBottomDIO.Controls.Add(this.panel9);
this.pandBottomDIO.Dock = System.Windows.Forms.DockStyle.Bottom;
- this.pandBottomDIO.Location = new System.Drawing.Point(1, 725);
+ this.pandBottomDIO.Location = new System.Drawing.Point(1, 745);
this.pandBottomDIO.Margin = new System.Windows.Forms.Padding(0);
this.pandBottomDIO.Name = "pandBottomDIO";
- this.pandBottomDIO.Size = new System.Drawing.Size(1278, 55);
+ this.pandBottomDIO.Size = new System.Drawing.Size(1278, 35);
this.pandBottomDIO.TabIndex = 136;
//
// panel9
@@ -1730,7 +1729,7 @@ namespace Project
this.panel9.Location = new System.Drawing.Point(0, 0);
this.panel9.Margin = new System.Windows.Forms.Padding(0);
this.panel9.Name = "panel9";
- this.panel9.Size = new System.Drawing.Size(1278, 55);
+ this.panel9.Size = new System.Drawing.Size(1278, 35);
this.panel9.TabIndex = 0;
//
// IOState
@@ -1797,7 +1796,7 @@ namespace Project
this.IOState.ShadowColor = System.Drawing.Color.Transparent;
this.IOState.showDebugInfo = false;
this.IOState.ShowIndexString = false;
- this.IOState.Size = new System.Drawing.Size(1151, 55);
+ this.IOState.Size = new System.Drawing.Size(1151, 35);
this.IOState.TabIndex = 6;
this.IOState.Tags = null;
this.IOState.Text = "gridView2";
@@ -1870,7 +1869,7 @@ namespace Project
this.SSInfo.ShadowColor = System.Drawing.Color.Transparent;
this.SSInfo.showDebugInfo = false;
this.SSInfo.ShowIndexString = false;
- this.SSInfo.Size = new System.Drawing.Size(127, 55);
+ this.SSInfo.Size = new System.Drawing.Size(127, 35);
this.SSInfo.TabIndex = 10;
this.SSInfo.Tags = null;
this.SSInfo.Text = "gridView3";
@@ -1892,7 +1891,7 @@ namespace Project
this.panDlg.Location = new System.Drawing.Point(1, 146);
this.panDlg.Margin = new System.Windows.Forms.Padding(0);
this.panDlg.Name = "panDlg";
- this.panDlg.Size = new System.Drawing.Size(1014, 579);
+ this.panDlg.Size = new System.Drawing.Size(1014, 599);
this.panDlg.TabIndex = 146;
//
// arPanel2
diff --git a/Cs_HMI/Project/fMain.cs b/Cs_HMI/Project/fMain.cs
index 2a273c2..d586464 100644
--- a/Cs_HMI/Project/fMain.cs
+++ b/Cs_HMI/Project/fMain.cs
@@ -64,9 +64,6 @@ namespace Project
if (PUB.setting.FullScreen) this.WindowState = FormWindowState.Maximized;
- lbTime.Click += (s1,e1) => {
- PUB.mapctl.ShowDesign();
- };
}
protected override void WndProc(ref Message m)
@@ -108,7 +105,7 @@ namespace Project
this.ctlPos1.Invalidate();
}
-
+
private void __Closing(object sender, FormClosingEventArgs e)
{
PUB.popup.needClose = true;
@@ -216,9 +213,10 @@ namespace Project
//배터리관리시스템
PUB.BMS = new arDev.BMS();
PUB.BMS.BMSDataReceive += Bms_BMSDataReceive;
+ PUB.BMS.BMSCellDataReceive += BMS_BMSCellDataReceive;
PUB.BMS.Message += Bms_Message;
PUB.BMS.ChargeDetect += BMS_ChargeDetect;
-
+ PUB.BMS.ScanInterval = PUB.setting.interval_bms;//
//디버그메세지 출력용 소켓
PUB.sock_debug = new Device.Socket();
@@ -243,11 +241,12 @@ namespace Project
PUB.sm.SPS += sm_SPS;
PUB.sm.Start();
+ tmDisplay.Tick += tmDisplay_Tick;
tmDisplay.Start(); //start Display
this.btDebug.Visible = PUB.setting.UseDebugMode;
PUB.log.Add("Program Start");
-
+
//수량표시
PUB.counter.PropertyChanged += (s1, e1) => Update_Count();
@@ -263,6 +262,8 @@ namespace Project
}
+
+
#region "Mouse Form Move"
private Boolean fMove = false;
@@ -303,7 +304,7 @@ namespace Project
}
PUB.log.Add("WS << " + e.rawData);
}
-
+
void socket_RecvMessage(object sender, Device.Socket.SocketMessageEventArgs e)
{
if (e.Message.isError)
@@ -640,7 +641,7 @@ namespace Project
}
VAR.BOOL[eVarBool.FLAG_SETUP] = false;// VAR.BOOL[eVarBool.FLAG_SETUP] = false;//VAR.BOOL[eVarBool.FLAG_SETUP] = false;
-
+
if (popmsg) PUB.popup.Visible = true;
}
@@ -714,7 +715,7 @@ namespace Project
SetScreen(form_flag);
MenuFlag.ForeColor = Color.Gold;
}
-
+
private void btLog_Click(object sender, EventArgs e)
{
if (form_log == null || form_log.IsDisposed || form_log.Disposing)
@@ -886,9 +887,5 @@ namespace Project
}
}
- private void lbTime_Click(object sender, EventArgs e)
- {
- PUB.mapctl.ShowDesign();
- }
}
}
diff --git a/Cs_HMI/Project/fMain.resx b/Cs_HMI/Project/fMain.resx
index ca93129..f62c95d 100644
--- a/Cs_HMI/Project/fMain.resx
+++ b/Cs_HMI/Project/fMain.resx
@@ -126,163 +126,160 @@
- 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
+ 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
+ hk4AAAAASUVORK5CYII=
- iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAb9JREFUWEft
- lr8vBEEUxxchEST8ARIkOo1KqFwpUQgKdFRKKoVG+AtUSirFSkiuVCk2m9zt7e7dJtcIkVxFcK4QCYld
- 3517e9ZkHGLfRuEln8zsvLf7/e7M7A/tP8rlckehUFgCG03IUHnygYsfgeALfMuy5uiUZAMXf5bEPqOa
- y+UG6bTkQiHUjCq4/CU2ZnOK5H9sICkedF1vUxnwgQuOgc6JYRg9soFbMBaOlUqlLs/z+jgJgqD1gwGs
- yyIYQf8iGmOmBr2FyMCLaZqdaA/pOC1uIgMuzcYVHadFJTKw77puL9pwE8pFbGAJssKAbdtraDPxZErs
- CANwMgnWpSQ70JwPDfjhY4H2IJ5MAyz7sNh4tAGL8WQKPIp3ATon9En+7kcpETD9ZnjjGjpbYFRVxMye
- MIDODAysSMk0WBUGsBEGcLArJdlxHGdcGMBGaMF74ExVxMgr9l13wwAGalIBN+dCPAys/5CigBud5DUN
- 0z+rKGAFN71J8uIp2JYLuMFNT5O8WIKsqoiTfD7fT/JiBipyATP3JF0PDNxJBdycknQ9sB7LGHySiri4
- xvRPkPR7INEu/7Vy0PgT/juhaW/ZS5SxdhC40QAAAABJRU5ErkJggg==
+ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAa9JREFUWEfl
+ ls8rhEEch/2IEoo/QKHcXJzEiaNyEA64cXLk5OAi/gInR04Or6IcnRy2t/b9fGbHbr0XkdoTYTlIUfbV
+ +/a+b3aalux+N2XquWzPNE/T7Mzb1PTvh+/77SSXSG5UYdKcV7dB8ohk8A1lAHPm3LoMkm+WBW2Ustns
+ gDm/5mFZqBolktc1ogBM/TagXjw5jtNqCyiT1CSPSTqSZDKZbjPgnuRo+Fs+n+8sFAq9kgRB0FIRAGAR
+ wDDJK8uWSfAMYCEJeHddt4PkoUWU5C4J0PFu3FgkSYpJwL7Wuic+hKYkBoDTKEAptRZet6bQAHaiAAAT
+ ANYtgigA5sOAcvi3IHlgCtJorYeigxcfwAtTEOYlugtInsRP8k8fpboAwI0uIgBbAEZMoQHsJTfhDIAV
+ iyDNahSgte4nuWsRRMnlcmNRQBAEzUqpc1MQ5sP3/a40IHwYLJIkl9Hi8SEctAjSOGmAUmrWIogCYDMN
+ ILltCtIopabTgPBFMgVpPM/r+7oDRVMQ5jFdPA54sEiSnFUEKKWWSb5aRAluPc8brwgIB8k286tVgvRL
+ +C+NT9lLlLEeyHt7AAAAAElFTkSuQmCC
- iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAABAlJREFUWEft
- V91vFFUU3wiC6AMBY0xIgb13ltJAjBoJMSF+BYkPIgkJH/ogxo8QP6ha1MDandn7qAkxmsiDoYkhJoUA
- LyQYQwj1BfkPeFAeBLozszWh0PJR0XbX3296ZvbOfnVteewvOdmZc37nzJk7555zNzOP/4uqyTxQctWT
- ZaO3Bq56L3D1+77n7C6Z7FPVvc88KLT7D991NvlF/WPg6b8g1RYyFhb1scCo58Vt7ghNbh0Cn6l7UCcy
- RF8JMztwaRHothX0Hyz5Ofx+UvL05tDNrS8Z3Y1P8Zzvqn2w/QzbvxZ/wnf1OxIuE+Zzj3WcVOg5X1iB
- KkFR/zRSyDlibonQZLNI5KTlS8kP93Utwe8IpOIb/bLQm8P39BskivN44DnbxdQxUDOvw3dMYlQR08TX
- kD6hNWK4oNeA8LcQbwVGbxBTCpfM+kVXD65aRuG1qFMI3ewLiDEhseIXap8AjHHBVXxPvSbqBqA+eoXX
- MiCSXwXbKYvXlp9hMcUkbidRJwg81Y+l/J7fE1vycBKwqI5e7s0txvW35AidL/NHwklLiwSKzhEhTAX9
- arWoIwSuU7ACXIPcs+5Z+VeTe1d9Oe2jzya6tDQmwC4Gww0SsHXOizoB3vI7K0BbwQ5y6VPdmVnAXTFi
- urUtowf00iiojcBke5IgrvOxqBP8abIPwdauC8ZyZVYt2TdqWxwExbdF1Oj9Ly7kG4WeOg6bXckXOBM4
- A3D9m6Xn5xikD30lzMxA8bwpASrXzMoVosbKrHhY9Cnhw4WSkSQaOPQVSoTQ1R9C/03588cfEVUN053K
- OYTl3yuqCC0TwEOFgqaTe7oZx07A73dWxnqszluinhlRIbnOntDTX8F5MgmOZWcSfDg+z0VLf48FyxlA
- XwmDFc6+EnPa9ZeWGDZdy+E8GgdpI8P1S09gS34aczjARF0Dt0b9dikXVqv4LTiMrIfMJANRUAvoDb+K
- 7XrT4oShTwhpQTOhPRq3tV3AphP3eAqbEpsTryvkRkEFfBHopyJ7Uf0g6jRgbJ6Ap38XCka0+oi1wJ6A
- Vv11zGFbjnSwkSP0BCjuEwkXJytRpwFjswROcaAIJQU87IOYx8Ek6gZwlMc8FPNpUTcCBDuBeKknOFKF
- kkJH49iojYhxR2LdbXugASFJoO7wMMYDitA6Bj7FTvjekhhToat2iak50BxeIhES8I3wmxfnacExi4NF
- 6C3BA010Mq6tYgXJfCbm9vALam0p3/Oo3PJo9jYC3JVAlElsp1+iHYGjN/k8mHJ2IMH9nKLg2IfScXyG
- HRJudpBj+ZAVtCNhwTFBCTN3yGlpEHLTflCdlCEDZZN9VtzuPzjrOQlZVFh2/C1T72JLvlp29RP82ya0
- eXSITOY/N4AkP6QQbUkAAAAASUVORK5CYII=
+ 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
- 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
+ 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
- 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
- nZx+lOd9AOxqphdZ7gKgAAAAAElFTkSuQmCC
+ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAPJJREFUWEft
+ lj0KwkAQhVNY6n0Uj6BtagvxEFqJvYWnEL1EmiDKvEmXG9gK3kDZsIZk3Jj4s7iEffBB2Pl5r9iEBIGX
+ lyti5j6A/Qt2ADYAJnEc9+S8OlM13aN65XyO8pLzAYAQwK0hVyKaPmaZeabODH1VhGX39wNkENESwEqe
+ N+A3Ab7AeoC13le103qA3MBQK9VztS1AHS0PQESHwofnqW49gAt3wAfwAf4bwIXXsA4HAxDR0NBohSRJ
+ BtI/iKKoA+Akmy1wVF7SP1Oapl1mXjDzVv5IGrgUlqpnWS+hd86Vh/T9SMw8BnDWjGTdy6up7uxqphcq
+ UCfvAAAAAElFTkSuQmCC
- iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABGdBTUEAALGPC/xhBQAABHFJREFUWEe1
- mFtIXFcUhifk8pqSPORCSGn7VPIcaFpCA+lDSUjfpIRCI97vKN5vGUEUhEFFRGkfRBEVa0GtdzQ+WBE1
- EPRF0XhDJAreo45Dra78a7tncs6e7ajjnAUfnnPW2mv97r32uYzNXwsODv4iPDz8+8jIyN9BVERERBzO
- Q8BPYWFh92SYtRYdHf01CueBd+AQkA/mQQWLlsMDZ0j6AMn/BqeJOIlR8Fym89+CgoKuIVER+E8mvij/
- xMTE3Jbpz2chISF3kYD/M13ii7CCHnssy5zNMOgbwD2gSxgI9tEGv8hyvg3Bd8CcYbBVuKKiop7IsnrL
- y8u7gsB/lYFWshEaGvqlLO9tCMhVBlyI2NhYyszM1Prc4B72BqUvHSswGBrtKwQ41QGnkZ6eToODg1RU
- VOTlKygoIKfTSTU1NV4+IxD1Usr4bLj4py74JBBPVVVVtLu7S2xtbW0mPwvlv/n5+bS9vS1ijX6F97jF
- XJZSbDZM7U1cdClBJ5KVlUWTk5NCiNtUQUtLS9TX10doXCosLKS9vT0xzhhjxLTrcBKjC1LhWamtrSWX
- yyVlfDZVUHJyMs3NzVF7e7s47+npoZGREVOMEWhoknKEoA5dUFxcHBUXF1NJSQmVl5fT/Py8LO9tbkH1
- 9fVi9hITEykjI0Msqd1up7S0NDo4OKCUlBSvOpKP4CrruYSDbYPDAxc5q7kF4QFMo6OjNDw8LM67u7up
- t7dXHE9PT1N1dbUnvwom5iFv9fuqww0nOqsZlywhIUHMTE5Ojmjq9fV1cb2lpYUGBgY8cRpe8XZ/rHEI
- /BXEDA0NUWNjo+i7w8NDsfy806ampkxxCnbe7s80DsFFBDU3N4tdxsebm5titrgfFxcXTXEKDssE8e7q
- 7OwUx7zls7OzqbS0lGZnZ01xCg7Llmx8fFw0MPeTccnGxsZMcQp2S5qab4D7+/uUlJREFRUV4n7E13kJ
- Ozo6PHEaXvnc9rzmExMTgpmZGVlab25BLIb7hHuIz3lG+GbKxysrK+RwODz5VcS2Z8NJu+rUUVZWRhsb
- G1KC2dyCFhYWqLW1VeyuyspK8RzjZWMha2tr4lGi5pV4bowsKNrg8An3At/sjo6OpJRjU3uIZ3dnZ0f8
- EyyCm7murs4Uo/CXEMOGl6QbuHDmhyvDrxvLy8tSjllQQ0MDbW1tCTF83tXVJQT5mB2e0RdSzrHh4h9q
- 0GnwCxg/NHkXNTU1ea7zErEvPj6e+vv7aXV1lVJTU01jFaZNrx9s/CoJx7lf0Bh+98HnjekaP+15lrip
- +djoU8Hs/CplmA3ObDXYX7ipc3NztT6FXlne23jakOiNZpBVrIP7srze+MsSQbOGQVbB32Y/yrK+DcH8
- oWjlt5nTa1edZhjEH4zDhiSB4gO2/w+yzPkMg6+CAhCoHxua8TC/JdP7b0jyLaa4EQn9+jkGvTKC8T/L
- dIEz/phE8tco8hb8byyqYQaUQ8gjOdxaQ7HrEPgdBP6Gou6f9IJx/JR/ypFh5zSb7RMJw7xfBlQNxwAA
- AABJRU5ErkJggg==
+ iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAABGdBTUEAALGPC/xhBQAABGpJREFUWEe1
+ WFtIdFUUnp8ur0U9dCGK6il6DrrwU1AP8Ue9DRFBvzhnrz0XFcX77R9BFARREVHqQRRRMQM172g+mIga
+ hL4o+ntDJAXvqaNkumJt5gznrLMbxxnng485Z1/W+mbvtfbluFxxIiUl5UUhxMdSyh+klF4ASBNCpAoh
+ vjAM4w3ePinw+XzvAEAZAPwJANcAgFG4AQCNJJrbSRhCiPcB4JcYRPwf5wDgK273znC73c8DQBUA/KNx
+ Eg9/9fv9r3I/MSE1NfX18D/jRhPlrmEYD7m/qACAd8MxwI3dFy+EEN9wv1oAwGsAsK4xct+89Hq9n3H/
+ NpSVlT0LAL9rOieLhx6P5y2uIwIAKNV0ipuBQAALCwsd5VZKKX9zuVwPuBaXYRhvA0CId7iN+fn5ODU1
+ hVVVVY66iooKDIVC2Nra6qizUkr5HdfjklL+xBtGo5QSm5ub8ezsDAn9/f22ehJKv+Xl5XhycqLachsW
+ PnW73c9ExAQCgZcpyDQNtSwqKsKlpSUlxAQXtL29jePj4+j1erGyshLPz89VP27LpC3rhBB+3kBHGpW2
+ tja8vLy0idEJys7OxvX1dRwYGFDvo6OjODs767BpUgjRbRU0yBsQ09LSsKamBmtra7GhoQE3Nja4jghM
+ QR0dHWr0MjMzsaCgQE1pMBjEvLw8vLq6wpycHIefMP8GgOdIzwMAONE0UE5ihSnI5/Ph3NwczszMqPeR
+ kREcGxtTzysrK9jS0uLwY1II8QGl+pu8wiQZihXWKcvIyFAjU1JSooL64OBAlff29uLk5KTDj4WPKd0f
+ aioSEkScnp7Grq4uFXfX19dq+inTlpeXHX4sDFK6P9JUJCyop6dHZRk9Hx0dqdGieNza2nL4sbA6aYIo
+ u4aGhtQzpXxxcTHW1dXh2tqaw49NULKmbGFhQQUwxZN1yubn5x1+LAwmJahpAby4uMCsrCxsbGxU6xGV
+ 0xQODg46/Fj4OGra05wvLi4qrq6ucg02mIJIDMUJxRC904jQYkrPu7u7WF1d7fBjUqU9AQAGeKWO9fX1
+ eHh4yLUomII2Nzexr69PZVdTU5Pax2jaSMj+/r7aSrjdMCMLIwnyaRpoSbFAi93NzY1WkEka3dPTU/Un
+ SAQFc3t7u8OehT9Htg6Px/PSXTZXIh03dnZ2tII6Ozvx+PhYiaH34eFhJSjK6NCIfh0RFB6lH3mj20gH
+ MNo0KYu6u7sj5TRFVJeeno4TExO4t7eHubm5jv4WrtiOHwQ6SsZzQCPS2cfv99vKaLenUaKgpmfex0op
+ 5bc2MSYAoJg3jpcU1KWlpY5yDce4jgho2OiMq+mULB7QOsh12EA3SwBY03S+b9Ld7FPuX4vwRTGZd7OQ
+ I6tuQ/jCOKMxlij/8nq9n3B/MYFWTgCouMePDT2GYbzC/dwZhmG8J6XsivdzjBBiVkr5JbebMOgyKYR4
+ AgB/AMC/3DHjKgA0SCk/4naSAgB4wTCMD4UQ31s+6aVIKT+nTzm8faz4DwnDvF8jQGuEAAAAAElFTkSu
+ QmCC
- iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAlxJREFUWEfV
- l81u00AUhcOrIMGqgg0sYEMFe1QE5QlQd63oug/QbstT0B2UVgEKgW1+7GxoWSAeghKJSgGF70bHlpnc
- cYbW5edIn6LcuefesT0e263/Ur1e7/pgMHiZZdmB0e/327Cg4fMXzTdhUoUJbGj4/MVRP3cm8FTDzYiC
- DyjcodkVhUoR/1RtLg41XMq8xDvUuq9QmjA+xDhW4RP+P55MJhdsjOt/i9h3jVUZ53l+w3Isl/9r8K0Y
- g2UbmyubrQxhg3fwIYh55NR448THSWeCpF3H3Ai2dtQmLiZwkeTj0NwAo263e1lt6sVMV5wCZ+WRys+X
- LaLIdTwV1GqrdJq0ij+GhQJM2yIDL2cKZ7Sn0mnidrrjFRJfOKK7Si1FfAmia8e2b6XOSnv7lu6Az/Cj
- MIZ4zQtxpPc8j7C9wzawZ7DJQV6TbTr7ffBMv0DzvixRkVd7OSrsyzK99dpOgse2LFGR8yTwuFhPWf6B
- CRDYCxMiZLJERc4w8MTYk2V6BhZgg+AOHIH3LChYkm1G1LDniOcxrOYh7Fgv6ynbrFjNtyvGkGNb7Uot
- peZfg9yS2tswVOJGlINdb6P2tP/2RmQTwPTeK3ZKXltNlZ8vTue6U+SsrKp8vWxxkFy8yTTJCbvfVbWJ
- i8ROYGySt2oTl1bzzG3ImjjgN+mVDF4FMSPtlcxE8rIZZBzRfEVDNsFFYt7DasytdlNpxUvNqBiDtJfS
- QjZb2B0Oh5cUKkUxe2KGEzjScCnzWo3kI08VzV4EzW1vb/bDpE403HIm8Oc+zfQC8/c+TptTq/UT/J7v
- URSJG9cAAAAASUVORK5CYII=
+ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAjpJREFUWEfV
+ V7tu1UAQDb+CBBUiTSigAZE+SsTjCxAdCGo+ANrkK6DjkYiEd3v3nNnbkFBEfESSKxHpgi4atGutx2Nj
+ uA4RRzqN53HG9nh2vLDwPyKEcInktoi8VQJ4DeCC9TsxkHxMclYSwCPrd2IQkRdOAU+t31wAcJPkBxG5
+ aG0k920BJHetn8ZqDgA3rK0TInKb5DQlPhaRB7PZ7IzaQghXSX53CpjGGC+rj/qSvE/yW7aRvGV1XGi1
+ hXjJjyQ/O9ctI4B3zvVprycB4KUTPAi1d6xeAwDOkjy0wQNwMhqNzls9FyJy10kwL+9YnVZoE7W8x7+i
+ Diqr0YnUxV9sIkPFeqI49ooiEqxGJ2KMyzZJwQMAKzaG5GpX7+j4tjEV0mx/kr6AryR/2ASZnniGiKxZ
+ /4I6O3SAPddRHmNcqgJJbjkBDQJATdHB715Hwa0qSJvEcfC4XlNzQHLDiWuw1pinXgDJTevQQqmpOSA5
+ duI8blZBukzoeU7yGcm9lrMgc7WmWCCdI9Y/U3PuqoZqdS4wInLdSZB5qN1uY5L4keP/i52foUXPQRTT
+ +1Z2PvY/HkRagIh8sonm4Ju8T/QCgIdOknl5z+q40OYoNpkheRxjXLR6DegO5wQPxfdWr4G2lUz3/74r
+ Gckd53q/lUyhC2RRxEQXlGwDcK3lsJqGEK5kv7TUTLKt91KaodXq6Tgej89ZWzoxbQF71k9jNUfvO+8L
+ kq9sAYP/mHRBdwengH/3a3bqP6dD4if8nu9RoGmrKgAAAABJRU5ErkJggg==
- iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAsRJREFUWEft
- lz9TU0EUxdOovWKv9Golo59AsRMr0ELQRjsHO4n4CWD8A/oNUD9IZpKQxCYVIg1gKTYChejvvDmbbN57
- IS8gY8OZOZPHuefe3b27+xJKpzgKqtXqhdXV1Sv1ev0OHNezNIdPBgw0Cl8xWAv+6cNmo9GYZzKXnXZ8
- NJvNixR+A/ejgQZR3tdwxGWOBlZzgyLbLhq4g77C5xyfD0U/S9uxJ3Cbro253HAg8R4FdqNiX9Em+Txj
- SwaKwSm4DkPeLpObsKUYSNDKO4Mz8Lt2u33W4YGQl7ylkA93C3fCe/49JJP4zKGhQf5sqAO3Ct0UjDpw
- SRKdeGs5QZECaQ91lkM9FrNoOR8YRjGG074et53O3ETTIXtsKQPF5JHXUmltbe0c2jeomvuHXlEmoHse
- ZjtpWYVHYDjhv2FmEtIck0fezhWkkw+siy8tZ0Hwi0076UPHhGbQwwAH8KlDebEnDiXgb92OH443LPdC
- e2eD9n7Fcg+IxatMOpGn2d4D9E/2HFQqlfOWu2Dfrtmg9r+wnAHx9IADBxeIzdmnBV613AWD6kslGKYt
- 5yLVcjHT9jScE+rfttyFxGCQ2XIu+kygcybyQM6j4GesW5a7+O9bEB9CJvDRcg+IHecQfrYn/xAKBJs2
- FbmGnT3PifVsh19GPxXHW7ecBa2ZdxFxyrIm9i9fRGXLWeg1ieEkXsUbUDX3Wq3WJYfygUm/ZMJslywn
- SH/R5CHtocb7UI9OLFjuD4xqd/wraNahoUHu86jOZpEFJOCcjJEQ/xpaVisdHgh5We2HKP9XrVa77nAx
- UGCCxHgSG2j307cjhmI+cGHPk8HR7toyHNyJraiYqOukL5YyhadFPVtLrlrEzaFXnob2jYksUmyYn+V7
- TGyh8J4XAcV0RbXSRjRQmoqVB16140KvUgbSv2bjop77vl5PcShKpb/SGsNeH5IytgAAAABJRU5ErkJg
- gg==
+ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAplJREFUWEft
+ V7tuFEEQdALkPHIgByIQfAE2GRAZCMCQmAyZDA74AhAPG/7AwIectNfVeyQXnY0TbEJMgu2Ah+rUc/Q2
+ s3d7xojEJY20mqrumenpnpmdmtrHLlAUxREAp0TkkojM8Jt9UbenEJGTIvIEQBfAz5pWqurjoihORPtd
+ oyzLYwBeANjJDFjXqH0O4Gj0NxFU9TyAjeB8U1WXATxU1Zts9s2+zaDdEJFz0W8jiMhVAFvO2YqIzAI4
+ ELUJ5ABcA7Dq7LZU9UrUjoStfDi4iLzq9XoHo64O1AJY9JNoHAnb889u8HtR0xQAFtwk1htViiXcwEhV
+ X3quiYOoAbDkFvPMc3+ApeayfdWHvSzLC5Zkd6pWv0GOGmpTX7/fPwTgo/ncGVmiVudptrOpn+XkMvx7
+ bhI2ODlqqB2WoKrecFvxqGrpAOBDchCTTkTm3AA/ANwdwc17W6uOL8ar54aw4zXt/XLkibDKQSRyfdGO
+ APAuTbDdbh+OPPf4jAv/g8gnZAYcOzjBw8ot8HTkGUZeKklwK/IeIeTZsEeYTfI/HXkmyrSLwFzkPWom
+ MMyJHETktpvAxcj//y3wSSgibyNPZAafJAnfp2hlk5DgfW6iJmU43PMMV9kOO4y+2uLEcxXwMZGiwFst
+ 9e/xQdSqWjrwmPxHR/Ga+dzudrvHq1YB9pJJs130XLxocogaAK+TP1V96rksLNz+FbQQNU0B4L7z8ylO
+ rhZ8PITX0BJDGXV1oFZV3zj7b51O52zUjQSfUWESa6p6PVaHBzlLuLTng8FV9XLUNoJFYt05Y2M58WJp
+ 8chm47f1DUrNh33ilUdw3/iSmfBZvs2Ea7znTWAlypVqZsDUyLXGltrfgkep/ZrNpF+z2uN1H2PwC9Ia
+ w16WomAQAAAAAElFTkSuQmCC
- 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
+ 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
+ p5p6qCR+AaqNxnTXLPGFAAAAAElFTkSuQmCC
diff --git a/Cs_HMI/Project/fSetup.Designer.cs b/Cs_HMI/Project/fSetup.Designer.cs
index aa5a324..310af6b 100644
--- a/Cs_HMI/Project/fSetup.Designer.cs
+++ b/Cs_HMI/Project/fSetup.Designer.cs
@@ -1270,7 +1270,7 @@
this.label30.AutoSize = true;
this.label30.Font = new System.Drawing.Font("궁서체", 18F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.label30.ForeColor = System.Drawing.Color.WhiteSmoke;
- this.label30.Location = new System.Drawing.Point(946, 273);
+ this.label30.Location = new System.Drawing.Point(946, 188);
this.label30.Name = "label30";
this.label30.Size = new System.Drawing.Size(49, 24);
this.label30.TabIndex = 33;
@@ -1281,15 +1281,15 @@
this.label12.AutoSize = true;
this.label12.Font = new System.Drawing.Font("궁서체", 18F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.label12.ForeColor = System.Drawing.Color.WhiteSmoke;
- this.label12.Location = new System.Drawing.Point(946, 194);
+ this.label12.Location = new System.Drawing.Point(946, 109);
this.label12.Name = "label12";
- this.label12.Size = new System.Drawing.Size(36, 24);
+ this.label12.Size = new System.Drawing.Size(49, 24);
this.label12.TabIndex = 33;
- this.label12.Text = "ms";
+ this.label12.Text = "sec";
//
// button2
//
- this.button2.Location = new System.Drawing.Point(383, 178);
+ this.button2.Location = new System.Drawing.Point(383, 93);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(89, 56);
this.button2.TabIndex = 28;
@@ -1309,7 +1309,7 @@
"46800",
"115200",
"250000"});
- this.tbBaudBAT.Location = new System.Drawing.Point(478, 181);
+ this.tbBaudBAT.Location = new System.Drawing.Point(478, 96);
this.tbBaudBAT.Name = "tbBaudBAT";
this.tbBaudBAT.Size = new System.Drawing.Size(217, 51);
this.tbBaudBAT.TabIndex = 27;
@@ -1319,7 +1319,7 @@
//
this.tbportBMS.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
this.tbportBMS.Font = new System.Drawing.Font("궁서체", 32F, System.Drawing.FontStyle.Bold);
- this.tbportBMS.Location = new System.Drawing.Point(161, 178);
+ this.tbportBMS.Location = new System.Drawing.Point(161, 93);
this.tbportBMS.Name = "tbportBMS";
this.tbportBMS.Size = new System.Drawing.Size(216, 56);
this.tbportBMS.TabIndex = 26;
@@ -1330,7 +1330,7 @@
this.label27.AutoSize = true;
this.label27.Font = new System.Drawing.Font("궁서체", 18F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.label27.ForeColor = System.Drawing.Color.WhiteSmoke;
- this.label27.Location = new System.Drawing.Point(38, 194);
+ this.label27.Location = new System.Drawing.Point(38, 109);
this.label27.Name = "label27";
this.label27.Size = new System.Drawing.Size(114, 24);
this.label27.TabIndex = 25;
@@ -1387,7 +1387,7 @@
//
// btSelXbee
//
- this.btSelXbee.Location = new System.Drawing.Point(383, 257);
+ this.btSelXbee.Location = new System.Drawing.Point(383, 172);
this.btSelXbee.Name = "btSelXbee";
this.btSelXbee.Size = new System.Drawing.Size(89, 56);
this.btSelXbee.TabIndex = 13;
@@ -1407,7 +1407,7 @@
"46800",
"115200",
"250000"});
- this.tbBaudXBE.Location = new System.Drawing.Point(478, 260);
+ this.tbBaudXBE.Location = new System.Drawing.Point(478, 175);
this.tbBaudXBE.Name = "tbBaudXBE";
this.tbBaudXBE.Size = new System.Drawing.Size(217, 51);
this.tbBaudXBE.TabIndex = 8;
@@ -1417,7 +1417,7 @@
//
this.tbPortXBE.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
this.tbPortXBE.Font = new System.Drawing.Font("궁서체", 32F, System.Drawing.FontStyle.Bold);
- this.tbPortXBE.Location = new System.Drawing.Point(161, 257);
+ this.tbPortXBE.Location = new System.Drawing.Point(161, 172);
this.tbPortXBE.Name = "tbPortXBE";
this.tbPortXBE.Size = new System.Drawing.Size(216, 56);
this.tbPortXBE.TabIndex = 3;
@@ -1428,7 +1428,7 @@
this.label5.AutoSize = true;
this.label5.Font = new System.Drawing.Font("궁서체", 18F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.label5.ForeColor = System.Drawing.Color.WhiteSmoke;
- this.label5.Location = new System.Drawing.Point(38, 273);
+ this.label5.Location = new System.Drawing.Point(38, 188);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(114, 24);
this.label5.TabIndex = 1;
@@ -1444,7 +1444,7 @@
this.valIntervalBMS.FontSideButton = new System.Drawing.Font("Consolas", 15F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.valIntervalBMS.ForeColor = System.Drawing.Color.White;
this.valIntervalBMS.ForeColorButton = System.Drawing.Color.Black;
- this.valIntervalBMS.Location = new System.Drawing.Point(701, 181);
+ this.valIntervalBMS.Location = new System.Drawing.Point(701, 96);
this.valIntervalBMS.MaxValue = 999999D;
this.valIntervalBMS.MinValue = 0D;
this.valIntervalBMS.Name = "valIntervalBMS";
@@ -1467,7 +1467,7 @@
this.valIntervalXBE.FontSideButton = new System.Drawing.Font("Consolas", 15F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.valIntervalXBE.ForeColor = System.Drawing.Color.White;
this.valIntervalXBE.ForeColorButton = System.Drawing.Color.Black;
- this.valIntervalXBE.Location = new System.Drawing.Point(701, 260);
+ this.valIntervalXBE.Location = new System.Drawing.Point(701, 175);
this.valIntervalXBE.MaxValue = 100D;
this.valIntervalXBE.MinValue = 0D;
this.valIntervalXBE.Name = "valIntervalXBE";
diff --git a/Cs_HMI/Project/fSetup.resx b/Cs_HMI/Project/fSetup.resx
index d7f6461..9b5df3d 100644
--- a/Cs_HMI/Project/fSetup.resx
+++ b/Cs_HMI/Project/fSetup.resx
@@ -120,41 +120,40 @@
- iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAsRJREFUWEft
- lz9TU0EUxdOovWKv9Golo59AsRMr0ELQRjsHO4n4CWD8A/oNUD9IZpKQxCYVIg1gKTYChejvvDmbbN57
- IS8gY8OZOZPHuefe3b27+xJKpzgKqtXqhdXV1Sv1ev0OHNezNIdPBgw0Cl8xWAv+6cNmo9GYZzKXnXZ8
- NJvNixR+A/ejgQZR3tdwxGWOBlZzgyLbLhq4g77C5xyfD0U/S9uxJ3Cbro253HAg8R4FdqNiX9Em+Txj
- SwaKwSm4DkPeLpObsKUYSNDKO4Mz8Lt2u33W4YGQl7ylkA93C3fCe/49JJP4zKGhQf5sqAO3Ct0UjDpw
- SRKdeGs5QZECaQ91lkM9FrNoOR8YRjGG074et53O3ETTIXtsKQPF5JHXUmltbe0c2jeomvuHXlEmoHse
- ZjtpWYVHYDjhv2FmEtIck0fezhWkkw+siy8tZ0Hwi0076UPHhGbQwwAH8KlDebEnDiXgb92OH443LPdC
- e2eD9n7Fcg+IxatMOpGn2d4D9E/2HFQqlfOWu2Dfrtmg9r+wnAHx9IADBxeIzdmnBV613AWD6kslGKYt
- 5yLVcjHT9jScE+rfttyFxGCQ2XIu+kygcybyQM6j4GesW5a7+O9bEB9CJvDRcg+IHecQfrYn/xAKBJs2
- FbmGnT3PifVsh19GPxXHW7ecBa2ZdxFxyrIm9i9fRGXLWeg1ieEkXsUbUDX3Wq3WJYfygUm/ZMJslywn
- SH/R5CHtocb7UI9OLFjuD4xqd/wraNahoUHu86jOZpEFJOCcjJEQ/xpaVisdHgh5We2HKP9XrVa77nAx
- UGCCxHgSG2j307cjhmI+cGHPk8HR7toyHNyJraiYqOukL5YyhadFPVtLrlrEzaFXnob2jYksUmyYn+V7
- TGyh8J4XAcV0RbXSRjRQmoqVB16140KvUgbSv2bjop77vl5PcShKpb/SGsNeH5IytgAAAABJRU5ErkJg
- gg==
+ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAplJREFUWEft
+ V7tuFEEQdALkPHIgByIQfAE2GRAZCMCQmAyZDA74AhAPG/7AwIectNfVeyQXnY0TbEJMgu2Ah+rUc/Q2
+ s3d7xojEJY20mqrumenpnpmdmtrHLlAUxREAp0TkkojM8Jt9UbenEJGTIvIEQBfAz5pWqurjoihORPtd
+ oyzLYwBeANjJDFjXqH0O4Gj0NxFU9TyAjeB8U1WXATxU1Zts9s2+zaDdEJFz0W8jiMhVAFvO2YqIzAI4
+ ELUJ5ABcA7Dq7LZU9UrUjoStfDi4iLzq9XoHo64O1AJY9JNoHAnb889u8HtR0xQAFtwk1htViiXcwEhV
+ X3quiYOoAbDkFvPMc3+ApeayfdWHvSzLC5Zkd6pWv0GOGmpTX7/fPwTgo/ncGVmiVudptrOpn+XkMvx7
+ bhI2ODlqqB2WoKrecFvxqGrpAOBDchCTTkTm3AA/ANwdwc17W6uOL8ar54aw4zXt/XLkibDKQSRyfdGO
+ APAuTbDdbh+OPPf4jAv/g8gnZAYcOzjBw8ot8HTkGUZeKklwK/IeIeTZsEeYTfI/HXkmyrSLwFzkPWom
+ MMyJHETktpvAxcj//y3wSSgibyNPZAafJAnfp2hlk5DgfW6iJmU43PMMV9kOO4y+2uLEcxXwMZGiwFst
+ 9e/xQdSqWjrwmPxHR/Ga+dzudrvHq1YB9pJJs130XLxocogaAK+TP1V96rksLNz+FbQQNU0B4L7z8ylO
+ rhZ8PITX0BJDGXV1oFZV3zj7b51O52zUjQSfUWESa6p6PVaHBzlLuLTng8FV9XLUNoJFYt05Y2M58WJp
+ 8chm47f1DUrNh33ilUdw3/iSmfBZvs2Ea7znTWAlypVqZsDUyLXGltrfgkep/ZrNpF+z2uN1H2PwC9Ia
+ w16WomAQAAAAAElFTkSuQmCC
- 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
+ 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
+ p5p6qCR+AaqNxnTXLPGFAAAAAElFTkSuQmCC
diff --git a/Cs_HMI/StateMachine/StateMachine.csproj b/Cs_HMI/StateMachine/StateMachine.csproj
index 15580ba..2e24ea1 100644
--- a/Cs_HMI/StateMachine/StateMachine.csproj
+++ b/Cs_HMI/StateMachine/StateMachine.csproj
@@ -48,5 +48,11 @@
+
+
+ {c5f7a8b2-8d3e-4a1b-9c6e-7f4d5e2a9b1c}
+ AGVNavigationCore
+
+
\ No newline at end of file
diff --git a/Cs_HMI/SubProject/AGV/Command.cs b/Cs_HMI/SubProject/AGV/Command.cs
index 1115b06..346fbb6 100644
--- a/Cs_HMI/SubProject/AGV/Command.cs
+++ b/Cs_HMI/SubProject/AGV/Command.cs
@@ -53,8 +53,8 @@ namespace arDev
else sb.Append("0S");
sb.Append("00");
sb.Append("0000"); //재기동시간
- if (opt == eStopOpt.MarkStop)
- VAR.BOOL[eVarBool.NEXTSTOP_MARK] = true;
+ //if (opt == eStopOpt.MarkStop)
+ // VAR.BOOL[eVarBool.NEXTSTOP_MARK] = true;
//동작중이면 이 멈춤 명령을 기록으로 남긴다 230116
if (this.system1.agv_run)
diff --git a/Cs_HMI/SubProject/BMS/.gitignore b/Cs_HMI/SubProject/BMS/.gitignore
index ff91f4d..553bd67 100644
--- a/Cs_HMI/SubProject/BMS/.gitignore
+++ b/Cs_HMI/SubProject/BMS/.gitignore
@@ -1,12 +1,12 @@
-*.suo
-*.user
-*.pdb
-bin
-obj
-desktop.ini
-.vs
-.git
-packages
-*.patch
-/Project/SourceSetup.exe
+*.suo
+*.user
+*.pdb
+bin
+obj
+desktop.ini
+.vs
+.git
+packages
+*.patch
+/Project/SourceSetup.exe
*.zip
diff --git a/Cs_HMI/SubProject/BMS/BMS.cs b/Cs_HMI/SubProject/BMS/BMS.cs
index 7bc656e..0305002 100644
--- a/Cs_HMI/SubProject/BMS/BMS.cs
+++ b/Cs_HMI/SubProject/BMS/BMS.cs
@@ -11,15 +11,9 @@ namespace arDev
{
public class BMS : arRS232
{
-
- ///
- /// 데이터조회간격(초)
- ///
- public float ScanInterval { get; set; }
-
public BMS()
{
- ScanInterval = 1000;
+
MinRecvLength = 34;
}
@@ -29,7 +23,6 @@ namespace arDev
public event EventHandler BMSDataReceive;
public event EventHandler BMSCellDataReceive;
-
protected override bool CustomParser(byte[] buf, out byte[] remainBuffer)
{
List remain = new List();
@@ -91,10 +84,7 @@ namespace arDev
}
bool Recv0 = false;
- int Recv0Cnt = 0;
bool Recv1 = false;
- int Recv1Cnt = 0;
-
public event EventHandler ChargeDetect;
public override bool ProcessRecvData(byte[] data)
{
@@ -112,29 +102,19 @@ namespace arDev
return false;
}
-
- if (VerifyChecksum(data) == false)
- {
- RaiseMessage(MessageType.Error, "Checksum error");
- return false;
- }
-
-
- if (data[1] == 0x03)
+ if (QueryIndex == 0)
{
return ParseBMSInfo();
}
- else if (data[1] == 0x04)
+ else
{
return ParseBMSCellVoltage();
}
- else return false;
+
}
bool ParseBMSCellVoltage()
{
- if (UseCmdLogging == true)
- RaiseMessage(MessageType.Recv, ByteListToHexString(LastReceiveBuffer.ToList()));
//var i = 0;
var BatteryCell_Checksum = 0xffff;// - (LastReceiveBuffer[i + 3] + LastReceiveBuffer[i + 4] + LastReceiveBuffer[i + 5] + LastReceiveBuffer[i + 6] + LastReceiveBuffer[i + 7] + LastReceiveBuffer[i + 8] + LastReceiveBuffer[i + 9] + LastReceiveBuffer[i + 10] + LastReceiveBuffer[i + 11] + LastReceiveBuffer[i + 12] + LastReceiveBuffer[i + 13] + LastReceiveBuffer[i + 14] + LastReceiveBuffer[i + 15] + LastReceiveBuffer[i + 16] + LastReceiveBuffer[i + 17] + LastReceiveBuffer[i + 18] + LastReceiveBuffer[i + 19])) + 1;
for (int i = 3; i < 20; i++)
@@ -170,6 +150,7 @@ namespace arDev
try
{
BMSCellDataReceive?.Invoke(this, new BMSCelvoltageEventArgs(v1, v2, v3, v4, v5, v6, v7, v8));
+ Current_CellTime = DateTime.Now;
return true;
}
catch (Exception ex)
@@ -182,62 +163,8 @@ namespace arDev
}
-
-
-
- public static bool VerifyChecksum(byte[] dataBytes)
- {
- // 길이 바이트 (byte[3])
- byte length = dataBytes[3];
-
- if (dataBytes[1] == 0x03)
- {
-
- // 데이터 바이트 (byte[4] ~ byte[30])
- byte[] data = dataBytes.Skip(4).Take(27).ToArray();
-
- // 길이와 데이터의 합을 계산
- ushort sum = (ushort)(length + data.Sum(b => b));
-
- // 반전시키고 높은 위치부터 낮은 위치로 더한 후 1을 더함
- ushort checksum = (ushort)((~sum + 1) & 0xFFFF);
-
- // 받은 체크섬 (byte[31] ~ byte[32])
- ushort receivedChecksum = (ushort)((dataBytes[31] << 8) | dataBytes[32]);
-
- //252 1: 64514
- // 계산된 체크섬과 받은 체크섬을 비교
- return checksum == receivedChecksum;
-
- }
- else if (dataBytes[1] == 0x04)
- {
- // 데이터 바이트 (byte[4] ~ byte[30])
- byte[] data = dataBytes.Skip(4).Take(16).ToArray();
-
- // 길이와 데이터의 합을 계산
- ushort sum = (ushort)(length + data.Sum(b => b));
-
- // 반전시키고 높은 위치부터 낮은 위치로 더한 후 1을 더함
- ushort checksum = (ushort)((~sum + 1) & 0xFFFF);
-
- // 받은 체크섬 (byte[31] ~ byte[32])
- ushort receivedChecksum = (ushort)((dataBytes[20] << 8) | dataBytes[21]);
-
- //252 1: 64514
- // 계산된 체크섬과 받은 체크섬을 비교
- return checksum == receivedChecksum;
-
- }
-
- return false;
- }
-
bool ParseBMSInfo()
{
- if(UseCmdLogging == true)
- RaiseMessage(MessageType.Recv, ByteListToHexString(LastReceiveBuffer.ToList()));
-
//전압확인
UInt16 batH = (UInt16)LastReceiveBuffer[4];
UInt16 batL = (UInt16)LastReceiveBuffer[5];
@@ -265,7 +192,6 @@ namespace arDev
float levraw = (float)(Current_Amp / (Current_MaxAmp * 1.0));
Current_Level = (float)(levraw * 100.0);// (float)(map(remain, 0, total, 0, 100));
Current_LevelA = LastReceiveBuffer[23]; //<- 23번자료는 byte이므로 소수점이잇는 데이터로 직접 계산한다
- Current_DataTime = DateTime.Now;
//250620 jwlee 추가
@@ -282,6 +208,7 @@ namespace arDev
try
{
BMSDataReceive?.Invoke(this, new BMSInformationEventArgs(Current_Volt, Current_Amp, Current_MaxAmp, Current_Level, Changed));
+ Current_DataTime = DateTime.Now;
return true;
}
catch (Exception ex)
@@ -424,13 +351,10 @@ namespace arDev
public int Current_MaxAmp { get; set; }
public DateTime Current_DataTime = DateTime.Parse("1982-11-23");
+ public DateTime Current_CellTime = DateTime.Parse("1982-11-23");
public int QueryIndex { get; set; } = 0;
- public int CMDOutCnt { get; set; } = 3;
-
- public bool UseCmdLogging { get; set; } = false;
-
///
/// 상태읽기와 전압읽기명령을 반복합니다
///
@@ -447,19 +371,7 @@ namespace arDev
}
else
{
- if (Recv0Cnt > CMDOutCnt)
- {
- Recv0Cnt = 0;
- RaiseMessage(MessageType.Error, "0 명령 Count Out");
- QueryIndex = 1;
- Recv1 = false;
- return true;
- }
- else
- {
- return SendQuery_ReadStatue();
- }
-
+ return SendQuery_ReadStatue();
}
}
else
@@ -472,25 +384,13 @@ namespace arDev
}
else
{
- if (Recv1Cnt > CMDOutCnt)
- {
- Recv1Cnt = 0;
- RaiseMessage(MessageType.Error, "1 명령 Count Out");
- QueryIndex = 0;
- Recv0 = false;
- return true;
- }
- else
- {
- return SendQuery_ReadCellvoltage();
- }
+ return SendQuery_ReadCellvoltage();
}
}
}
public Boolean SendQuery_ReadStatue()
{
- Recv0Cnt++;
Recv0 = false;
var cmd = new List();
cmd.Add(0xDD);
@@ -501,16 +401,12 @@ namespace arDev
cmd.Add(0xFD);
cmd.Add(0x77);
cmd.Add(0x0D);
- if (UseCmdLogging == true)
- RaiseMessage(MessageType.Normal, ByteListToHexString(cmd));
-
return WriteData(cmd.ToArray());
}
public Boolean SendQuery_ReadCellvoltage()
{
- Recv1Cnt++;
Recv1 = false;
var cmd = new List();
cmd.Add(0xDD);
@@ -521,21 +417,8 @@ namespace arDev
cmd.Add(0xFC);
cmd.Add(0x77);
cmd.Add(0x0D);
- if (UseCmdLogging == true)
- RaiseMessage(MessageType.Normal, ByteListToHexString(cmd));
return WriteData(cmd.ToArray());
}
-
- public string ByteListToHexString(List byteList)
- {
- StringBuilder hex = new StringBuilder(byteList.Count * 2);
- foreach (byte b in byteList)
- {
- hex.AppendFormat("{0:X2} ", b);
- }
- return hex.ToString();
- }
-
}
}
diff --git a/Cs_HMI/SubProject/BMS/BMS.csproj b/Cs_HMI/SubProject/BMS/BMS.csproj
index a10e852..e8ca0f5 100644
--- a/Cs_HMI/SubProject/BMS/BMS.csproj
+++ b/Cs_HMI/SubProject/BMS/BMS.csproj
@@ -9,7 +9,7 @@
Properties
arDevice
BMS
- v4.0
+ v4.8
512
true
@@ -46,7 +46,12 @@
-
+
+
+
+ {14e8c9a5-013e-49ba-b435-efefc77dd623}
+ CommData
+
\ No newline at end of file
diff --git a/Cs_HMI/SubProject/BMS/RS232.cs b/Cs_HMI/SubProject/BMS/RS232.cs
index 0cd07a5..e12bbd9 100644
--- a/Cs_HMI/SubProject/BMS/RS232.cs
+++ b/Cs_HMI/SubProject/BMS/RS232.cs
@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using System.Text;
-using System.ComponentModel;
using System.Threading;
-using System.Threading.Tasks;
+
public abstract partial class arRS232 : IDisposable
{
@@ -15,7 +15,7 @@ public abstract partial class arRS232 : IDisposable
/// 최종 전송 메세지
///
public byte[] lastSendBuffer = new byte[] { };
-
+ //public int ValidCheckTimeMSec { get; set; } = 5000;
protected List tempBuffer = new List();
protected Boolean findSTX = false;
public string errorMessage { get; set; }
@@ -26,7 +26,10 @@ public abstract partial class arRS232 : IDisposable
/// 메세지 수신시 사용하는 내부버퍼
///
protected List _buffer = new List();
-
+ ///
+ /// 데이터조회간격(초)
+ ///
+ public float ScanInterval { get; set; }
// public byte[] LastRecvData;
public string LastRecvString
@@ -98,7 +101,7 @@ public abstract partial class arRS232 : IDisposable
{
_device = new System.IO.Ports.SerialPort();
this.BaudRate = 9600;
-
+ ScanInterval = 10;
_device.DataReceived += barcode_DataReceived;
_device.ErrorReceived += this.barcode_ErrorReceived;
_device.WriteTimeout = 5000;
@@ -292,11 +295,11 @@ public abstract partial class arRS232 : IDisposable
}
catch (Exception ex)
{
- if (IsOpen)
- {
- //_device.DiscardInBuffer();
- //_device.DiscardOutBuffer();
- }
+ //if (IsOpen)
+ //{
+ // //_device.DiscardInBuffer();
+ // //_device.DiscardOutBuffer();
+ //}
errorMessage = ex.Message;
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
}
@@ -413,7 +416,7 @@ public abstract partial class arRS232 : IDisposable
protected abstract bool CustomParser(byte[] buf, out byte[] remainBuffer);
///
- /// 데이터수신시간이 설정값보다 x 2.5를 초과하면 false 가 반환됨
+ /// 포트가 열려있거나 데이터 수신시간이 없는경우 false를 반환합니다
///
public Boolean IsValid
{
@@ -422,10 +425,16 @@ public abstract partial class arRS232 : IDisposable
if (IsOpen == false) return false;
if (lastRecvTime.Year == 1982) return false;
var ts = DateTime.Now - lastRecvTime;
- if (ts.TotalSeconds > 5) return false;
+ if (ts.TotalSeconds > (this.ScanInterval * 2.5)) return false;
return true;
}
}
+
+ protected string ByteListToHexString(List src)
+ {
+ return string.Join(" ", src.Select(t => t.ToString("X2")));
+ }
+
protected bool WriteData(string cmd)
{
return WriteData(System.Text.Encoding.Default.GetBytes(cmd));
@@ -482,4 +491,5 @@ public abstract partial class arRS232 : IDisposable
}
return bRet;
}
-}
\ No newline at end of file
+}
+
diff --git a/Cs_HMI/SubProject/CommData/RS232.cs b/Cs_HMI/SubProject/CommData/RS232.cs
index e3f9c7a..27c28ae 100644
--- a/Cs_HMI/SubProject/CommData/RS232.cs
+++ b/Cs_HMI/SubProject/CommData/RS232.cs
@@ -16,7 +16,7 @@ namespace arDev
/// 최종 전송 메세지
///
public byte[] lastSendBuffer = new byte[] { };
-
+ //public int ValidCheckTimeMSec { get; set; } = 5000;
protected List tempBuffer = new List();
protected Boolean findSTX = false;
public string errorMessage { get; set; }
@@ -27,7 +27,10 @@ namespace arDev
/// 메세지 수신시 사용하는 내부버퍼
///
protected List _buffer = new List();
-
+ ///
+ /// 데이터조회간격(초)
+ ///
+ public float ScanInterval { get; set; }
// public byte[] LastRecvData;
public string LastRecvString
@@ -99,7 +102,7 @@ namespace arDev
{
_device = new System.IO.Ports.SerialPort();
this.BaudRate = 9600;
-
+ ScanInterval = 10;
_device.DataReceived += barcode_DataReceived;
_device.ErrorReceived += this.barcode_ErrorReceived;
_device.WriteTimeout = 5000;
@@ -293,11 +296,11 @@ namespace arDev
}
catch (Exception ex)
{
- if (IsOpen)
- {
- //_device.DiscardInBuffer();
- //_device.DiscardOutBuffer();
- }
+ //if (IsOpen)
+ //{
+ // //_device.DiscardInBuffer();
+ // //_device.DiscardOutBuffer();
+ //}
errorMessage = ex.Message;
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
}
@@ -414,16 +417,16 @@ namespace arDev
protected abstract bool CustomParser(byte[] buf, out byte[] remainBuffer);
///
- /// 데이터수신시간이 설정값보다 x 2.5를 초과하면 false 가 반환됨
+ /// 포트가 열려있거나 데이터 수신시간이 없는경우 false를 반환합니다
///
- public Boolean IsValid
+ public Boolean IsValid
{
get
{
if (IsOpen == false) return false;
if (lastRecvTime.Year == 1982) return false;
var ts = DateTime.Now - lastRecvTime;
- if (ts.TotalSeconds > 5) return false;
+ if (ts.TotalSeconds > (this.ScanInterval * 2.5)) return false;
return true;
}
}
diff --git a/Cs_HMI/SubProject/EnigProtocol b/Cs_HMI/SubProject/EnigProtocol
index 77a9d40..070aa84 160000
--- a/Cs_HMI/SubProject/EnigProtocol
+++ b/Cs_HMI/SubProject/EnigProtocol
@@ -1 +1 @@
-Subproject commit 77a9d4066214342ca31118c01039fd23f7e57f53
+Subproject commit 070aa848c96ec7097ae70b5a12f19f328fb6ae03
diff --git a/Cs_HMI/TestProject/Test_BMS/Form1.Designer.cs b/Cs_HMI/TestProject/Test_BMS/Form1.Designer.cs
index 9f11f63..eddee98 100644
--- a/Cs_HMI/TestProject/Test_BMS/Form1.Designer.cs
+++ b/Cs_HMI/TestProject/Test_BMS/Form1.Designer.cs
@@ -102,7 +102,7 @@
//
// logTextBox1
//
- this.logTextBox1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(24)))), ((int)(((byte)(24)))));
+ this.logTextBox1.BackColor = System.Drawing.Color.WhiteSmoke;
this.logTextBox1.ColorList = new arCtl.sLogMessageColor[0];
this.logTextBox1.DateFormat = "yy-MM-dd HH:mm:ss";
this.logTextBox1.DefaultColor = System.Drawing.Color.LightGray;
diff --git a/Cs_HMI/TestProject/Test_BMS/Test_BMS.csproj b/Cs_HMI/TestProject/Test_BMS/Test_BMS.csproj
index d150d22..d2ef170 100644
--- a/Cs_HMI/TestProject/Test_BMS/Test_BMS.csproj
+++ b/Cs_HMI/TestProject/Test_BMS/Test_BMS.csproj
@@ -18,7 +18,7 @@
true
full
false
- ..\..\..\..\..\..\..\..\Amkor\AGV2\Test\BMS\
+ ..\..\..\..\..\..\Amkor\AGV4\
DEBUG;TRACE
prompt
4
diff --git a/Cs_HMI/TestProject/Test_Narumi/Form1.Designer.cs b/Cs_HMI/TestProject/Test_Narumi/Form1.Designer.cs
index ed19445..a1dc79f 100644
--- a/Cs_HMI/TestProject/Test_Narumi/Form1.Designer.cs
+++ b/Cs_HMI/TestProject/Test_Narumi/Form1.Designer.cs
@@ -50,6 +50,9 @@
this.button14 = new System.Windows.Forms.Button();
this.button15 = new System.Windows.Forms.Button();
this.button16 = new System.Windows.Forms.Button();
+ this.button17 = new System.Windows.Forms.Button();
+ this.button18 = new System.Windows.Forms.Button();
+ this.button19 = new System.Windows.Forms.Button();
this.panel1 = new System.Windows.Forms.Panel();
this.rt0 = new System.Windows.Forms.RichTextBox();
this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel();
@@ -58,9 +61,6 @@
this.rt3 = new System.Windows.Forms.RichTextBox();
this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel();
this.logTextBox2 = new arCtl.LogTextBox();
- this.button17 = new System.Windows.Forms.Button();
- this.button18 = new System.Windows.Forms.Button();
- this.button19 = new System.Windows.Forms.Button();
this.tableLayoutPanel1.SuspendLayout();
this.panel1.SuspendLayout();
this.tableLayoutPanel2.SuspendLayout();
@@ -130,7 +130,7 @@
//
// logTextBox1
//
- this.logTextBox1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(24)))), ((int)(((byte)(24)))));
+ this.logTextBox1.BackColor = System.Drawing.Color.WhiteSmoke;
this.logTextBox1.ColorList = new arCtl.sLogMessageColor[0];
this.logTextBox1.DateFormat = "ss.fff";
this.logTextBox1.DefaultColor = System.Drawing.Color.LightGray;
@@ -338,6 +338,39 @@
this.button16.UseVisualStyleBackColor = true;
this.button16.Click += new System.EventHandler(this.button16_Click);
//
+ // button17
+ //
+ this.button17.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.button17.Location = new System.Drawing.Point(3, 168);
+ this.button17.Name = "button17";
+ this.button17.Size = new System.Drawing.Size(130, 51);
+ this.button17.TabIndex = 17;
+ this.button17.Text = "Speed L";
+ this.button17.UseVisualStyleBackColor = true;
+ this.button17.Click += new System.EventHandler(this.button17_Click);
+ //
+ // button18
+ //
+ this.button18.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.button18.Location = new System.Drawing.Point(139, 168);
+ this.button18.Name = "button18";
+ this.button18.Size = new System.Drawing.Size(130, 51);
+ this.button18.TabIndex = 17;
+ this.button18.Text = "Speed M";
+ this.button18.UseVisualStyleBackColor = true;
+ this.button18.Click += new System.EventHandler(this.button18_Click);
+ //
+ // button19
+ //
+ this.button19.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.button19.Location = new System.Drawing.Point(275, 168);
+ this.button19.Name = "button19";
+ this.button19.Size = new System.Drawing.Size(130, 51);
+ this.button19.TabIndex = 17;
+ this.button19.Text = "Speed H";
+ this.button19.UseVisualStyleBackColor = true;
+ this.button19.Click += new System.EventHandler(this.button19_Click);
+ //
// panel1
//
this.panel1.Controls.Add(this.comboBox1);
@@ -423,7 +456,7 @@
//
// logTextBox2
//
- this.logTextBox2.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(24)))), ((int)(((byte)(24)))));
+ this.logTextBox2.BackColor = System.Drawing.Color.WhiteSmoke;
this.logTextBox2.ColorList = new arCtl.sLogMessageColor[0];
this.logTextBox2.DateFormat = "ss.fff";
this.logTextBox2.DefaultColor = System.Drawing.Color.LightGray;
@@ -441,39 +474,6 @@
this.logTextBox2.TabIndex = 4;
this.logTextBox2.Text = "";
//
- // button17
- //
- this.button17.Dock = System.Windows.Forms.DockStyle.Fill;
- this.button17.Location = new System.Drawing.Point(3, 168);
- this.button17.Name = "button17";
- this.button17.Size = new System.Drawing.Size(130, 51);
- this.button17.TabIndex = 17;
- this.button17.Text = "Speed L";
- this.button17.UseVisualStyleBackColor = true;
- this.button17.Click += new System.EventHandler(this.button17_Click);
- //
- // button18
- //
- this.button18.Dock = System.Windows.Forms.DockStyle.Fill;
- this.button18.Location = new System.Drawing.Point(139, 168);
- this.button18.Name = "button18";
- this.button18.Size = new System.Drawing.Size(130, 51);
- this.button18.TabIndex = 17;
- this.button18.Text = "Speed M";
- this.button18.UseVisualStyleBackColor = true;
- this.button18.Click += new System.EventHandler(this.button18_Click);
- //
- // button19
- //
- this.button19.Dock = System.Windows.Forms.DockStyle.Fill;
- this.button19.Location = new System.Drawing.Point(275, 168);
- this.button19.Name = "button19";
- this.button19.Size = new System.Drawing.Size(130, 51);
- this.button19.TabIndex = 17;
- this.button19.Text = "Speed H";
- this.button19.UseVisualStyleBackColor = true;
- this.button19.Click += new System.EventHandler(this.button19_Click);
- //
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
diff --git a/Cs_HMI/TestProject/Test_Narumi/Test_Narumi.csproj b/Cs_HMI/TestProject/Test_Narumi/Test_Narumi.csproj
index 9c42ed6..4af921b 100644
--- a/Cs_HMI/TestProject/Test_Narumi/Test_Narumi.csproj
+++ b/Cs_HMI/TestProject/Test_Narumi/Test_Narumi.csproj
@@ -18,7 +18,7 @@
true
full
false
- ..\..\..\..\..\..\..\..\Amkor\AGV2\
+ ..\..\..\..\..\..\Amkor\AGV4\
DEBUG;TRACE
prompt
4
diff --git a/Cs_HMI/TestProject/Test_Port/App.config b/Cs_HMI/TestProject/Test_Port/App.config
new file mode 100644
index 0000000..193aecc
--- /dev/null
+++ b/Cs_HMI/TestProject/Test_Port/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Cs_HMI/TestProject/Test_Port/CSetting.cs b/Cs_HMI/TestProject/Test_Port/CSetting.cs
new file mode 100644
index 0000000..2452618
--- /dev/null
+++ b/Cs_HMI/TestProject/Test_Port/CSetting.cs
@@ -0,0 +1,561 @@
+using System;
+using System.Linq;
+using System.ComponentModel;
+using System.Drawing.Design;
+using System.Runtime.CompilerServices;
+using System.Management;
+using AR;
+
+namespace Project
+{
+ public class MyUITypeEditor : UITypeEditor
+ {
+ public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
+ {
+ using (var f = new System.Windows.Forms.Form
+ {
+ WindowState = System.Windows.Forms.FormWindowState.Normal,
+ StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen,
+ Size = new System.Drawing.Size(800, 400),
+ MaximizeBox = false,
+ MinimizeBox = false,
+ Text = "Select Port",
+ FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog
+ })
+ {
+ var lst = new System.Windows.Forms.ListBox
+ {
+ Font = new System.Drawing.Font("Consolas", 15, System.Drawing.FontStyle.Bold),
+ Dock = System.Windows.Forms.DockStyle.Fill,
+ };
+ lst.DoubleClick += (s1, e1) =>
+ {
+ if (lst.SelectedItem != null) f.DialogResult = System.Windows.Forms.DialogResult.OK;
+ };
+ using (var searcher = new ManagementObjectSearcher("SELECT * FROM WIN32_SerialPort"))
+ {
+ var portnames = System.IO.Ports.SerialPort.GetPortNames().OrderBy(t => t);
+ var ports = searcher.Get().Cast().ToList();
+ foreach (var port in portnames)
+ {
+ var desc = "";
+ var portInfo = ports.Where(t => t["DeviceId"].ToString() == port).FirstOrDefault();
+ if (portInfo != null) desc = portInfo["Caption"].ToString();
+ lst.Items.Add(string.Format("{0} - {1}", port, desc));
+ }
+ }
+ f.Controls.Add(lst);
+ if (f.ShowDialog() == System.Windows.Forms.DialogResult.OK)
+ {
+ var name = lst.SelectedItem.ToString().Split('-');
+ return name[0].Trim();
+ }
+ else
+ {
+ return base.EditValue(context, provider, value);
+ }
+ }
+
+ }
+ public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
+ {
+ return UITypeEditorEditStyle.Modal;
+ }
+ }
+
+ public class CSetting : AR.Setting
+ {
+ #region "log"
+
+ [Category("System Log"), DisplayName("Digital Input/Output"),
+ Description("입/출력 장치의 상태 변화를 기록 합니다.")]
+ public Boolean Log_IO { get; set; }
+ [Category("System Log"), DisplayName("Network Alive Check(Ping)"),
+ Description("서버와의 통신검사 로그를 기록 합니다")]
+ public Boolean Log_Ping { get; set; }
+ [Category("System Log"), DisplayName("S/M Step Change"),
+ Description("상태머신의 변화 상태를 기록 합니다.")]
+ public Boolean Log_StepChange { get; set; }
+ [Category("System Log"), DisplayName("Socket Recv Message"),
+ Description("서버 수신 메세지를 기록 합니다.")]
+ public Boolean LOg_SocketRecv { get; set; }
+
+ #endregion
+
+ #region "Communication"
+
+ [Category("Commnunication Setting"), DisplayName("XBee PortName"), Editor(typeof(MyUITypeEditor), typeof(UITypeEditor))]
+ public string Port_XBE { get; set; }
+
+ [Category("Commnunication Setting"), DisplayName("RFID PortName"), Editor(typeof(MyUITypeEditor), typeof(UITypeEditor))]
+ public string Port_AGV { get; set; }
+
+ [Category("Commnunication Setting"), DisplayName("Xbee ID"), Editor(typeof(MyUITypeEditor), typeof(UITypeEditor))]
+ public byte XBE_ID { get; set; }
+
+
+ [Category("Commnunication Setting"), DisplayName("BMS PortName"), Editor(typeof(MyUITypeEditor), typeof(UITypeEditor))]
+ public string Port_BAT { get; set; }
+
+ public int ChargerID { get; set; }
+
+ public int Baud_AGV { get; set; }
+ public int Baud_BAT { get; set; }
+ //public int Baud_PLC { get; set; }
+ public int Baud_XBE { get; set; }
+
+ //public int QueryInterval_BAT { get; set; }
+
+
+ #endregion
+
+ #region "Debug"
+ [Category("Developer Setting"), DisplayName("Listening Port"),
+ Description("서버와의 통신을 위한 대기 포트(TCP) - 기본값 : 7979")]
+ public int listenPort { get; set; }
+
+ [Category("Developer Setting")]
+ public Boolean UseDebugMode { get; set; }
+
+ #endregion
+
+ #region "Count Reset Setting"
+
+ [Category("Count Reset Setting"), DisplayName("A/M Clear Enable"),
+ Description("오전 초기화 여부\n작업수량을 초기화 하려면 True 로 설정하세요")]
+ public Boolean datetime_Check_1 { get; set; }
+ [Category("Count Reset Setting"), DisplayName("P/M Clear Enable"),
+ Description("오후 초기화 여부\n작업수량을 초기화 하려면 True 로 설정하세요")]
+ public Boolean datetime_Check_2 { get; set; }
+ [Category("Count Reset Setting"), DisplayName("A/M Clear Time(HH:mm)"),
+ Description("오전 초기화 시간(시:분)\n예) 09:00")]
+ public string datetime_Reset_1 { get; set; }
+ [Category("Count Reset Setting"), DisplayName("P/M Clear Time(HH:mm)"),
+ Description("오후 초기화 시간(시:분)\n예) 19:00")]
+ public string datetime_Reset_2 { get; set; }
+
+ #endregion
+
+ #region "general"
+
+
+
+
+ [Category("General Setting"), DisplayName("Enable Popup Message"),
+ Description("오류 발생시 별도의 메세지 창으로 표시합니다. 사용하지 않을 경우에는 우측 상단의 결과창의 메세지를 확인합니다.")]
+ public Boolean ShowPopUpMessage { get; set; }
+ [Category("General Setting"), DisplayName("Asset Number"),
+ Description("자산번호(서버에 값 전송 시 사용됩니다)")]
+ public string Asset { get; set; }
+
+ [Category("General Setting"),
+ Description("데이터 자동 소거 기간(일) 비활성=0")]
+ public int AutoDeleteDay { get; set; }
+ [Category("General Setting"),
+ Description("데이터 자동 소거 조건(남은용량 %)")]
+ public int AutoDeleteThreshold { get; set; }
+
+ [Category("General Setting"), PasswordPropertyText(true)]
+ public string Password_Setup { get; set; }
+
+ [Category("General Setting"), Browsable(false)]
+ public string Language { get; set; }
+
+ [Category("General Setting"), DisplayName("Full Screen Window State"),
+ Description("화면을 전체화면으로 사용 합니다.")]
+ public Boolean FullScreen { get; set; }
+ public bool DetectManualCharge { get; set; }
+ public Boolean StartLog { get; set; }
+
+ #endregion
+
+ #region "TAGS"
+ public int TAGPOT { get; set; }
+ public int TAGNOT { get; set; }
+ public int TAG_F2_F3 { get; set; }
+ public int TAG_QC_F1 { get; set; }
+ public int TAG_F1_F2 { get; set; }
+ public int TAG_F3_F4 { get; set; }
+ public int TAG_F4_F5 { get; set; }
+
+ public int TAG_QA_QC { get; set; }
+ public int TAGQAB { get; set; }
+ public int TAGQAA { get; set; }
+
+
+ public int TAGQCB { get; set; }
+ public int TAGQCA { get; set; }
+ public int TAGF1A { get; set; }
+ public int TAGF2A { get; set; }
+ public int TAGF3A { get; set; }
+ public int TAGF4A { get; set; }
+ public int TAGF5A { get; set; }
+
+ public int TAGF1B { get; set; }
+ public int TAGF2B { get; set; }
+ public int TAGF3B { get; set; }
+ public int TAGF4B { get; set; }
+ public int TAGF5B { get; set; }
+ #endregion
+
+ #region "Charge"
+ public int chargerpos { get; set; }
+ public int ChargetWaitSec { get; set; }
+ public int ChargeEmergencyLevel { get; set; }
+ public int ChargeMaxLevel { get; set; }
+ public int ChargeStartLevel { get; set; }
+ public Boolean Enable_AutoCharge { get; set; }
+
+ public int ChargeRetryTerm { get; set; }
+ //public int ChargeIdleInterval { get; set; }
+ public int ChargeMaxTime { get; set; }
+ public int ChargeSearchTime { get; set; }
+
+ #endregion
+
+ #region "AGV"
+
+ public bool AutoModeOffAndClearPosition { get; set; }
+ public string musicfile { get; set; }
+
+ ///
+ /// FVI로 가능 방향이 Backward 인가?
+ /// Forward 방향이라면 false 를 한다
+ ///
+ public Boolean AGV_Direction_FVI_Backward { get; set; }
+
+ public Boolean Enable_Speak { get; set; }
+
+ //public Boolean Disable_BMS { get; set; }
+ //public Boolean Log_BMS_Tx { get; set; }
+ //public Boolean Log_BMS_Rx { get; set; }
+ //public double ChargeLimitLow { get; set; }
+ //public double ChargeLimitHigh { get; set; }
+
+ public string AGV_PANID { get; set; }
+ public string AGV_CHANNEL { get; set; }
+ public string AGV_ADDRESS { get; set; }
+
+ public int SCK { get; set; }
+ public int SSK { get; set; }
+ public int STT { get; set; }
+ public int SAD { get; set; }
+
+ public byte ZSpeed { get; set; }
+ public int SPD_L { get; set; }
+ public int SPD_M { get; set; }
+ public int SPD_H { get; set; }
+ public int SPD_DRIVE { get; set; }
+
+ public int SPD_S { get; set; }
+ public int SPD_R { get; set; }
+
+
+ public int PID_PH { get; set; }
+ public int PID_PM { get; set; }
+ public int PID_PL { get; set; }
+ public int PID_IH { get; set; }
+ public int PID_IM { get; set; }
+ public int PID_IL { get; set; }
+ public int PID_DH { get; set; }
+ public int PID_DM { get; set; }
+ public int PID_DL { get; set; }
+
+ public int PID_PS { get; set; }
+ public int PID_IS { get; set; }
+ public int PID_DS { get; set; }
+
+ public double WheelSpeedCharge { get; set; }
+ public byte HomePositionValue { get; set; }
+ public byte HomeKitNo { get; set; }
+ public Single interval_xbe { get; set; }
+ public int interval_bms { get; set; }
+ public int doorSoundTerm { get; set; }
+ public int musicvol { get; set; }
+
+ public bool Enable_Music { get; set; }
+ #endregion
+
+ [Category("Report"),
+ Description("상태기록시 장비 식별코드(4자리)"),
+ DisplayName("M/C ID")]
+ public string MCID { get; set; }
+
+ [Category("Report"),
+ Description("작업 기록을 장비기술(EE) Database 에 기록 합니다. 원격으로 장비 현황을 모니터링 할 수 있습니다"),
+ DisplayName("SAVE EE-DB")]
+ public Boolean Save_EEDatabase { get; set; }
+ [Category("Report"), Description("상태값을 전송하는 간격(초)")]
+ public int StatusInterval { get; set; }
+
+
+ [Category("AutoReboot"),
+ DisplayName("자동재부팅시간"),
+ Description("기본값 14:00:00~14:05:00 , 재부팅은 장비가 대기(IDLE)일 때에만 동작하며 지정 시간 범위를 벗어나면 동작하지 않습니다")]
+ public String AutoRebootTimeStart { get; set; }
+ [Category("AutoReboot")]
+ public string AUtoRebootLastTime { get; set; }
+
+
+ public bool SetAutoModeOn { get; set; }
+ public override void AfterLoad()
+ {
+ if (StatusInterval < 10) StatusInterval = 300; //5분간격
+
+ if (SAD == 0) SAD = 999;
+ if (TAGF1A == 0)
+ {
+ TAGNOT = 9000;
+
+ TAGQAB = 9200;
+ TAGQCB = 9300;
+ TAGF1B = 9400;
+ TAGF2B = 9500;
+ TAGF3B = 9600;
+ TAGF4B = 9700;
+ TAGF5B = 9800;
+
+ TAGQAA = 9201;
+ TAGQCA = 9301;
+ TAGF1A = 9401;
+ TAGF2A = 9501;
+ TAGF3A = 9601;
+ TAGF4A = 9701;
+ TAGF5A = 9801;
+
+ TAGPOT = 9900;
+
+ TAG_QA_QC = 9250;
+ TAG_QC_F1 = 9350;
+ TAG_F1_F2 = 9450;
+ TAG_F2_F3 = 9550;
+ TAG_F3_F4 = 9650;
+ TAG_F4_F5 = 9750;
+ }
+
+ if(TAG_F4_F5 == 0)
+ {
+ TAG_F4_F5 = 9750;
+ TAGF5B = 9800;
+ TAGF5A = 9801;
+ }
+
+ if (SCK == 0) SCK = 10;
+ if (SSK == 0) SSK = 10;
+ if (STT == 0) STT = 30;
+ if (ChargerID == 0) ChargerID = 203;
+ if (ChargerID < 200) ChargerID += 200;
+
+ //자동충전요건
+ if (ChargetWaitSec == 0) ChargetWaitSec = 3;
+ if (ChargeStartLevel == 0) ChargeStartLevel = 85;
+ if (ChargeMaxLevel == 0) ChargeMaxLevel = 85;
+ if (ChargeEmergencyLevel == 0) ChargeEmergencyLevel = 30;
+ if (interval_bms == 0) interval_bms = 10;
+
+ //충전은 10분간격으로 재시도 한다
+ if (ChargeRetryTerm == 0) ChargeRetryTerm = 600;
+ if (doorSoundTerm == 0) doorSoundTerm = 15; //기본 15초
+ if (ChargeSearchTime == 0) ChargeSearchTime = 25;
+ //최대 충전진행 시간(기본 1시간)
+ if (ChargeMaxTime == 0) ChargeMaxTime = 3600;
+ // if (interval_iostate == 0 || interval_iostate == 255) interval_iostate = 100;
+ if (ZSpeed == 0) ZSpeed = 20;
+ if (interval_xbe == 0) interval_xbe = 5.0f;
+ if (HomePositionValue == 0) HomePositionValue = 4;
+ if (HomeKitNo == 0) HomeKitNo = 2;
+ if (datetime_Reset_1 == "") datetime_Reset_1 = "00:00";
+ if (datetime_Reset_2 == "") datetime_Reset_2 = "00:00";
+
+ if (SPD_H == 0)
+ {
+ SPD_DRIVE = 200;
+ SPD_H = 110;
+ SPD_M = 70;
+ SPD_L = 30;
+ SPD_S = 61;
+ SPD_R = 70;
+ }
+
+ if (PID_PH == 0)
+ {
+ PID_PH = 180; PID_PM = 240; PID_PL = 260;
+ PID_IH = 610; PID_IM = 640; PID_IL = 660;
+ PID_DH = 110; PID_DM = 140; PID_DL = 160;
+ PID_PS = 200; PID_IS = 620; PID_DS = 120;
+ }
+
+ if (WheelSpeedCharge == 0) WheelSpeedCharge = 10;
+
+ if (AutoDeleteThreshold == 0) AutoDeleteThreshold = 20;
+ if (Asset == "") Asset = "DEV_SPLIT";
+ if (listenPort == 0) listenPort = 7979;
+ if (Port_XBE == "") Port_XBE = "COM8";
+
+ if (Language.isEmpty()) Language = "Kor";
+ if (Password_Setup.isEmpty()) Password_Setup = "0000";
+ if (musicfile.isEmpty()) musicfile = UTIL.CurrentPath + "music.mp3";
+ if (musicvol == 0) musicvol = 50;
+
+
+ if (string.IsNullOrEmpty(Port_AGV)) Port_AGV = "COM1";
+ // if (string.IsNullOrEmpty(Port_PLC)) Port_PLC = "COM2";
+ if (string.IsNullOrEmpty(Port_XBE)) Port_XBE = "COM4";
+ if (string.IsNullOrEmpty(Port_BAT)) Port_BAT = "COM7";
+
+ if (Baud_AGV == 0) Baud_AGV = 57600;
+ //if (Baud_PLC == 0) Baud_PLC = 19200;
+ if (Baud_XBE == 0) Baud_XBE = 9600;
+ if (Baud_BAT == 0) Baud_BAT = 9600;
+
+
+ }
+ public override void AfterSave()
+ {
+ //throw new NotImplementedException();
+ }
+
+ public void CopyTo(CSetting dest)
+ {
+ //이곳의 모든 쓰기가능한 속성값을 대상에 써준다.
+ Type thClass = this.GetType();
+ foreach (var method in thClass.GetMethods())
+ {
+ //var parameters = method.GetParameters();
+ if (!method.Name.StartsWith("get_")) continue;
+
+ string keyname = method.Name.Substring(4);
+ string methodName = method.Name;
+
+ object odata = GetType().GetMethod(methodName).Invoke(this, null);
+ var wMethod = dest.GetType().GetMethod(Convert.ToString("set_") + keyname);
+ if (wMethod != null) wMethod.Invoke(dest, new object[] { odata });
+ }
+ }
+ }
+
+
+ public class CounterSetting : AR.Setting, INotifyPropertyChanged
+ {
+ public DateTime CountReset { get; set; }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ private int countUp1 = 0;
+ private int countUp2 = 0;
+ private int countUp3 = 0;
+ private int countUp4 = 0;
+ // private int countUp5 = 0;
+ private int countchgaerr = 0;
+ private int countchga = 0;
+ private int countchgm = 0;
+ //private int countdn = 0;
+ private int countqa = 0;
+ private int countqc = 0;
+
+ //메인카운터
+ public int CountUp1
+ {
+ get { return countUp1; }
+ set { if (value != countUp1) { countUp1 = value; NotifyPropertyChanged(); } }
+ }
+ public int CountUp2
+ {
+ get { return countUp2; }
+ set { if (value != countUp2) { countUp2 = value; NotifyPropertyChanged(); } }
+ }
+ public int CountUp3
+ {
+ get { return countUp3; }
+ set { if (value != countUp3) { countUp3 = value; NotifyPropertyChanged(); } }
+ }
+ public int CountUp4
+ {
+ get { return countUp4; }
+ set { if (value != countUp4) { countUp4 = value; NotifyPropertyChanged(); } }
+ }
+ //public int CountUp5
+ //{
+ // get { return countUp5; }
+ // set { if (value != countUp5) { countUp5 = value; NotifyPropertyChanged(); } }
+ //}
+
+ ///
+ /// 상차수량(FVI 1+2+3+4)
+ ///
+ public int CountUp
+ {
+ get
+ {
+ return CountUp1 + CountUp2 + CountUp3 + CountUp4;// + CountUp5;
+ }
+ }
+
+ public int CountChargeE
+ {
+ get { return this.countchgaerr; }
+ set { if (value != countchgaerr) { countchgaerr = value; NotifyPropertyChanged(); } }
+ }
+ public int CountChargeA
+ {
+ get { return this.countchga; }
+ set { if (value != countchga) { countchga = value; NotifyPropertyChanged(); } }
+ }
+ public int CountChargeM
+ {
+ get { return this.countchgm; }
+ set { if (value != countchgm) { countchgm = value; NotifyPropertyChanged(); } }
+ }
+
+ ///
+ /// 하차수량(QC+QA)
+ ///
+ public int CountDn
+ {
+ get { return this.countqa + this.countqc; }
+ //set { if (value != countdn) { countdn = value; NotifyPropertyChanged(); } }
+ }
+ public int CountQA
+ {
+ get { return this.countqa; }
+ set { if (value != countqa) { countqa = value; NotifyPropertyChanged(); } }
+ }
+ public int CountQC
+ {
+ get { return this.countqc; }
+ set { if (value != countqc) { countqc = value; NotifyPropertyChanged(); } }
+ }
+ public void CountClear()
+ {
+ CountUp1 = 0;
+ CountUp2 = 0;
+ CountUp3 = 0;
+ CountUp4 = 0;
+ // CountUp5 = 0;
+ CountQC = 0;
+ CountQA = 0;
+ //CountQa2 = 0;
+ CountReset = DateTime.Now;
+ PUB.log.Add("Count Clear");
+ this.Save();
+ }
+
+ public CounterSetting()
+ {
+ this.filename = AR.UTIL.CurrentPath + "counter.xml";
+ }
+ public override void AfterLoad()
+ {
+ if (CountReset == null) CountReset = DateTime.Parse("1982-11-23");
+ }
+ public override void AfterSave()
+ {
+ //throw new NotImplementedException();
+ }
+ }
+
+}
diff --git a/Cs_HMI/TestProject/Test_Port/Form1.Designer.cs b/Cs_HMI/TestProject/Test_Port/Form1.Designer.cs
new file mode 100644
index 0000000..5a3f91b
--- /dev/null
+++ b/Cs_HMI/TestProject/Test_Port/Form1.Designer.cs
@@ -0,0 +1,225 @@
+namespace Project
+{
+ partial class Form1
+ {
+ ///
+ /// 필수 디자이너 변수입니다.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// 사용 중인 모든 리소스를 정리합니다.
+ ///
+ /// 관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form 디자이너에서 생성한 코드
+
+ ///
+ /// 디자이너 지원에 필요한 메서드입니다.
+ /// 이 메서드의 내용을 코드 편집기로 수정하지 마세요.
+ ///
+ private void InitializeComponent()
+ {
+ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
+ this.label1 = new System.Windows.Forms.Label();
+ this.label2 = new System.Windows.Forms.Label();
+ this.cmbPortAGV = new System.Windows.Forms.ComboBox();
+ this.cmbPortBMS = new System.Windows.Forms.ComboBox();
+ this.tbBaudAGV = new System.Windows.Forms.TextBox();
+ this.tbBaudBMS = new System.Windows.Forms.TextBox();
+ this.btTestAGV = new System.Windows.Forms.Button();
+ this.btTestBMS = new System.Windows.Forms.Button();
+ this.rtLog = new System.Windows.Forms.RichTextBox();
+ this.btFindPort = new System.Windows.Forms.Button();
+ this.panel1 = new System.Windows.Forms.Panel();
+ this.toolStrip1 = new System.Windows.Forms.ToolStrip();
+ this.toolStripButton1 = new System.Windows.Forms.ToolStripButton();
+ this.toolStripButton2 = new System.Windows.Forms.ToolStripButton();
+ this.panel1.SuspendLayout();
+ this.toolStrip1.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // label1
+ //
+ this.label1.Location = new System.Drawing.Point(16, 10);
+ this.label1.Name = "label1";
+ this.label1.Size = new System.Drawing.Size(30, 20);
+ this.label1.TabIndex = 0;
+ this.label1.Text = "agv";
+ this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
+ //
+ // label2
+ //
+ this.label2.Location = new System.Drawing.Point(16, 36);
+ this.label2.Name = "label2";
+ this.label2.Size = new System.Drawing.Size(30, 20);
+ this.label2.TabIndex = 0;
+ this.label2.Text = "bms";
+ this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
+ //
+ // cmbPortAGV
+ //
+ this.cmbPortAGV.FormattingEnabled = true;
+ this.cmbPortAGV.Location = new System.Drawing.Point(60, 10);
+ this.cmbPortAGV.Name = "cmbPortAGV";
+ this.cmbPortAGV.Size = new System.Drawing.Size(121, 20);
+ this.cmbPortAGV.TabIndex = 1;
+ //
+ // cmbPortBMS
+ //
+ this.cmbPortBMS.FormattingEnabled = true;
+ this.cmbPortBMS.Location = new System.Drawing.Point(60, 36);
+ this.cmbPortBMS.Name = "cmbPortBMS";
+ this.cmbPortBMS.Size = new System.Drawing.Size(121, 20);
+ this.cmbPortBMS.TabIndex = 1;
+ //
+ // tbBaudAGV
+ //
+ this.tbBaudAGV.Location = new System.Drawing.Point(187, 10);
+ this.tbBaudAGV.Name = "tbBaudAGV";
+ this.tbBaudAGV.Size = new System.Drawing.Size(100, 21);
+ this.tbBaudAGV.TabIndex = 2;
+ //
+ // tbBaudBMS
+ //
+ this.tbBaudBMS.Location = new System.Drawing.Point(187, 36);
+ this.tbBaudBMS.Name = "tbBaudBMS";
+ this.tbBaudBMS.Size = new System.Drawing.Size(100, 21);
+ this.tbBaudBMS.TabIndex = 2;
+ //
+ // btTestAGV
+ //
+ this.btTestAGV.Location = new System.Drawing.Point(294, 8);
+ this.btTestAGV.Name = "btTestAGV";
+ this.btTestAGV.Size = new System.Drawing.Size(75, 24);
+ this.btTestAGV.TabIndex = 3;
+ this.btTestAGV.Text = "Test";
+ this.btTestAGV.UseVisualStyleBackColor = true;
+ this.btTestAGV.Click += new System.EventHandler(this.btTestAGV_Click);
+ //
+ // btTestBMS
+ //
+ this.btTestBMS.Location = new System.Drawing.Point(294, 34);
+ this.btTestBMS.Name = "btTestBMS";
+ this.btTestBMS.Size = new System.Drawing.Size(75, 24);
+ this.btTestBMS.TabIndex = 3;
+ this.btTestBMS.Text = "Test";
+ this.btTestBMS.UseVisualStyleBackColor = true;
+ this.btTestBMS.Click += new System.EventHandler(this.btTestBMS_Click);
+ //
+ // rtLog
+ //
+ this.rtLog.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.rtLog.Location = new System.Drawing.Point(0, 92);
+ this.rtLog.Name = "rtLog";
+ this.rtLog.Size = new System.Drawing.Size(450, 215);
+ this.rtLog.TabIndex = 4;
+ this.rtLog.Text = "";
+ //
+ // btFindPort
+ //
+ this.btFindPort.Location = new System.Drawing.Point(375, 11);
+ this.btFindPort.Name = "btFindPort";
+ this.btFindPort.Size = new System.Drawing.Size(71, 47);
+ this.btFindPort.TabIndex = 5;
+ this.btFindPort.Text = "find port";
+ this.btFindPort.UseVisualStyleBackColor = true;
+ this.btFindPort.Click += new System.EventHandler(this.btFindPort_Click);
+ //
+ // panel1
+ //
+ this.panel1.Controls.Add(this.cmbPortAGV);
+ this.panel1.Controls.Add(this.btFindPort);
+ this.panel1.Controls.Add(this.label1);
+ this.panel1.Controls.Add(this.label2);
+ this.panel1.Controls.Add(this.btTestBMS);
+ this.panel1.Controls.Add(this.cmbPortBMS);
+ this.panel1.Controls.Add(this.tbBaudAGV);
+ this.panel1.Controls.Add(this.tbBaudBMS);
+ this.panel1.Controls.Add(this.btTestAGV);
+ this.panel1.Dock = System.Windows.Forms.DockStyle.Top;
+ this.panel1.Location = new System.Drawing.Point(0, 25);
+ this.panel1.Name = "panel1";
+ this.panel1.Size = new System.Drawing.Size(450, 67);
+ this.panel1.TabIndex = 6;
+ //
+ // toolStrip1
+ //
+ this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.toolStripButton1,
+ this.toolStripButton2});
+ this.toolStrip1.Location = new System.Drawing.Point(0, 0);
+ this.toolStrip1.Name = "toolStrip1";
+ this.toolStrip1.Size = new System.Drawing.Size(450, 25);
+ this.toolStrip1.TabIndex = 7;
+ this.toolStrip1.Text = "toolStrip1";
+ //
+ // toolStripButton1
+ //
+ this.toolStripButton1.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButton1.Image")));
+ this.toolStripButton1.ImageTransparentColor = System.Drawing.Color.Magenta;
+ this.toolStripButton1.Name = "toolStripButton1";
+ this.toolStripButton1.Size = new System.Drawing.Size(53, 22);
+ this.toolStripButton1.Text = "Load";
+ this.toolStripButton1.Click += new System.EventHandler(this.toolStripButton1_Click);
+ //
+ // toolStripButton2
+ //
+ this.toolStripButton2.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right;
+ this.toolStripButton2.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButton2.Image")));
+ this.toolStripButton2.ImageTransparentColor = System.Drawing.Color.Magenta;
+ this.toolStripButton2.Name = "toolStripButton2";
+ this.toolStripButton2.Size = new System.Drawing.Size(52, 22);
+ this.toolStripButton2.Text = "Save";
+ this.toolStripButton2.Click += new System.EventHandler(this.toolStripButton2_Click);
+ //
+ // Form1
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(450, 307);
+ this.Controls.Add(this.rtLog);
+ this.Controls.Add(this.panel1);
+ this.Controls.Add(this.toolStrip1);
+ this.MaximizeBox = false;
+ this.MinimizeBox = false;
+ this.Name = "Form1";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+ this.Text = "AGV Port Scanner";
+ this.Load += new System.EventHandler(this.Form1_Load);
+ this.panel1.ResumeLayout(false);
+ this.panel1.PerformLayout();
+ this.toolStrip1.ResumeLayout(false);
+ this.toolStrip1.PerformLayout();
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.Label label2;
+ private System.Windows.Forms.ComboBox cmbPortAGV;
+ private System.Windows.Forms.ComboBox cmbPortBMS;
+ private System.Windows.Forms.TextBox tbBaudAGV;
+ private System.Windows.Forms.TextBox tbBaudBMS;
+ private System.Windows.Forms.Button btTestAGV;
+ private System.Windows.Forms.Button btTestBMS;
+ private System.Windows.Forms.RichTextBox rtLog;
+ private System.Windows.Forms.Button btFindPort;
+ private System.Windows.Forms.Panel panel1;
+ private System.Windows.Forms.ToolStrip toolStrip1;
+ private System.Windows.Forms.ToolStripButton toolStripButton1;
+ private System.Windows.Forms.ToolStripButton toolStripButton2;
+ }
+}
+
diff --git a/Cs_HMI/TestProject/Test_Port/Form1.cs b/Cs_HMI/TestProject/Test_Port/Form1.cs
new file mode 100644
index 0000000..ab890de
--- /dev/null
+++ b/Cs_HMI/TestProject/Test_Port/Form1.cs
@@ -0,0 +1,715 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using System.IO.Ports;
+using System.IO;
+using AR;
+
+namespace Project
+{
+
+ public partial class Form1 : Form
+ {
+
+
+ public Form1()
+ {
+ InitializeComponent();
+
+ PUB.init();
+
+ // 현재 설치되어있는 시리얼포트목록을 조회해서 cmbPort... 컨트롤에 미리 입력한다.
+ RefreshPortList();
+
+ // 기본 BaudRate 설정
+ tbBaudAGV.Text = "115200";
+ tbBaudBMS.Text = "9600";
+ }
+
+ ///
+ /// 시리얼 포트 목록을 새로고침하여 콤보박스에 추가
+ ///
+ private void RefreshPortList()
+ {
+ try
+ {
+ string[] ports = SerialPort.GetPortNames();
+
+ cmbPortAGV.Items.Clear();
+ cmbPortBMS.Items.Clear();
+
+ foreach (string port in ports)
+ {
+ cmbPortAGV.Items.Add(port);
+ cmbPortBMS.Items.Add(port);
+ }
+
+
+
+ addLog($"포트 목록 새로고침 완료: {ports.Length}개 포트 발견");
+ }
+ catch (Exception ex)
+ {
+ addLog($"포트 목록 조회 오류: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 설정파일에서 데이터를 읽어서 컨트롤에 설정한다
+ ///
+ void LoadSetting()
+ {
+ try
+ {
+
+ // AGV 설정
+ if (!string.IsNullOrEmpty(PUB.setting.Port_AGV))
+ {
+ this.cmbPortAGV.Text = PUB.setting.Port_AGV;
+ }
+ tbBaudAGV.Text = PUB.setting.Baud_AGV.ToString();
+
+ // BMS 설정
+ if (!string.IsNullOrEmpty(PUB.setting.Port_BAT))
+ {
+ cmbPortBMS.Text = PUB.setting.Port_BAT;
+ }
+ tbBaudBMS.Text = PUB.setting.Baud_BAT.ToString();
+
+ addLog("설정 파일 로드 완료");
+ }
+ catch (Exception ex)
+ {
+ addLog($"설정 로드 오류: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 현재 설정된 값을 확인하고 셋팅파일에 기록한다
+ ///
+ void SaveSetting()
+ {
+ try
+ {
+ // 현재 UI 값을 설정 객체에 반영
+ PUB.setting.Port_AGV = cmbPortAGV.SelectedItem?.ToString() ?? "";
+ PUB.setting.Baud_AGV = int.TryParse(tbBaudAGV.Text, out int agvBaud) ? agvBaud : 115200;
+ PUB.setting.Port_BAT = cmbPortBMS.SelectedItem?.ToString() ?? "";
+ PUB.setting.Baud_BAT = int.TryParse(tbBaudBMS.Text, out int bmsBaud) ? bmsBaud : 9600;
+
+ // 파일에 저장
+ PUB.setting.Save();
+
+ addLog("설정 파일 저장 완료");
+ }
+ catch (Exception ex)
+ {
+ addLog($"설정 저장 오류: {ex.Message}");
+ }
+ }
+
+ private void Form1_Load(object sender, EventArgs e)
+ {
+ LoadSetting();
+ }
+
+ private void btLoadSetting_Click(object sender, EventArgs e)
+ {
+ LoadSetting();
+ }
+
+ private void btSaveSetting_Click(object sender, EventArgs e)
+ {
+ SaveSetting();
+ }
+
+ private void btTestAGV_Click(object sender, EventArgs e)
+ {
+ // AGV 용 프로토콜을 전송해서 올바르게 피드백이 오는지 확인한다
+ // AGV Protocol: STX(0x02) + Data + ETX(0x03)
+
+ if (cmbPortAGV.SelectedItem == null)
+ {
+ addLog("[AGV] 포트를 선택해주세요");
+ return;
+ }
+
+ if (!int.TryParse(tbBaudAGV.Text, out int baudRate))
+ {
+ addLog("[AGV] BaudRate가 올바르지 않습니다");
+ return;
+ }
+
+ try
+ {
+ addLog($"[AGV] 테스트 시작: {cmbPortAGV.SelectedItem} @ {baudRate}bps");
+
+
+ using (arDev.Narumi port = new arDev.Narumi())
+ {
+ port.PortName = cmbPortAGV.SelectedItem.ToString();
+ port.BaudRate = baudRate;
+ port.Message += (s1, e1) =>{
+ addLog($"[AGV] 응답 수신: {e1.Message}");
+ };
+ port.Open();
+ addLog($"[AGV] 포트 열기 성공");
+
+ DateTime sendtime = DateTime.Now;
+ port.AGVMoveStop("test");//
+ addLog($"[AGV] 테스트 명령 전송:");
+
+ // 응답 대기
+ System.Threading.Thread.Sleep(1200);
+
+
+ if (port.lastRecvTime > sendtime)
+ {
+
+ // STX, ETX 확인
+ //if (response.Length >= 2 && response[0] == 0x02 && response[response.Length - 1] == 0x03)
+ //{
+ addLog($"[AGV] ✓ 통신 성공 - 올바른 프로토콜 응답");
+ //}
+ //else
+ //{
+ // addLog($"[AGV] ⚠ 응답은 받았으나 프로토콜 형식이 다름");
+ //}
+ }
+ else
+ {
+ addLog($"[AGV] ⚠ 응답 없음 - 포트는 열렸으나 장치 응답 없음");
+ }
+
+ port.Close();
+ addLog($"[AGV] 포트 닫기 완료");
+ }
+ }
+ catch (Exception ex)
+ {
+ addLog($"[AGV] ✗ 오류 발생: {ex.Message}");
+ }
+ }
+
+
+
+ private void btTestBMS_Click(object sender, EventArgs e)
+ {
+ // BMS 용 프로토콜을 전송해서 올바르게 피드백이 오는지 확인한다
+ // BMS Protocol: STX(0xDD) + Data(32 bytes) + ETX(0x77)
+
+ if (cmbPortBMS.SelectedItem == null)
+ {
+ addLog("[BMS] 포트를 선택해주세요");
+ return;
+ }
+
+ if (!int.TryParse(tbBaudBMS.Text, out int baudRate))
+ {
+ addLog("[BMS] BaudRate가 올바르지 않습니다");
+ return;
+ }
+
+ try
+ {
+ addLog($"[BMS] 테스트 시작: {cmbPortBMS.SelectedItem} @ {baudRate}bps");
+
+ using (SerialPort port = new SerialPort())
+ {
+ port.PortName = cmbPortBMS.SelectedItem.ToString();
+ port.BaudRate = baudRate;
+ port.DataBits = 8;
+ port.Parity = Parity.None;
+ port.StopBits = StopBits.One;
+ port.ReadTimeout = 3000;
+ port.WriteTimeout = 3000;
+
+ port.Open();
+ addLog($"[BMS] 포트 열기 성공");
+
+ // 간단한 테스트 명령 전송
+ var testCmd = new List();
+ testCmd.Add(0xDD);
+ testCmd.Add(0xA5);
+ testCmd.Add(0x03);
+ testCmd.Add(0x00);
+ testCmd.Add(0xFF);
+ testCmd.Add(0xFD);
+ testCmd.Add(0x77);
+ testCmd.Add(0x0D);
+
+ port.Write(testCmd.ToArray(), 0, testCmd.Count);//.Length);
+ addLog($"[BMS] 테스트 명령 전송: {BitConverter.ToString(testCmd.ToArray())}");
+
+ // 응답 대기
+ System.Threading.Thread.Sleep(1200);
+
+ if (port.BytesToRead > 0)
+ {
+ byte[] response = new byte[port.BytesToRead];
+ port.Read(response, 0, response.Length);
+ addLog($"[BMS] 응답 수신 ({response.Length} bytes): {BitConverter.ToString(response)}");
+
+ // STX, ETX 확인
+ if (response.Length >= 2 && response[0] == 0xDD && response[response.Length - 1] == 0x77)
+ {
+ addLog($"[BMS] ✓ 통신 성공 - 올바른 프로토콜 응답");
+ if (response.Length == 34 || response.Length == 23)
+ {
+ addLog($"[BMS] ✓ 데이터 길이 확인 ({response.Length} bytes)");
+ }
+ }
+ else
+ {
+ addLog($"[BMS] ⚠ 응답은 받았으나 프로토콜 형식이 다름");
+ }
+ }
+ else
+ {
+ addLog($"[BMS] ⚠ 응답 없음 - 포트는 열렸으나 장치 응답 없음");
+ }
+
+ port.Close();
+ addLog($"[BMS] 포트 닫기 완료");
+ }
+ }
+ catch (Exception ex)
+ {
+ addLog($"[BMS] ✗ 오류 발생: {ex.Message}");
+ }
+ }
+
+ void addLog(string msg)
+ {
+ // rtLog 컨트롤에 메세지를 추가하고, 추가된 메세지에 맞게 커서가 자동이동되게 한다
+ if (rtLog.InvokeRequired)
+ {
+ rtLog.Invoke(new Action(() => addLog(msg)));
+ }
+ else
+ {
+ string timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
+ rtLog.AppendText($"[{timestamp}] {msg}\r\n");
+ rtLog.SelectionStart = rtLog.Text.Length;
+ rtLog.ScrollToCaret();
+ }
+ }
+
+ private async void btFindPort_Click(object sender, EventArgs e)
+ {
+ // 버튼 비활성화 (중복 실행 방지)
+ btFindPort.Enabled = false;
+
+ try
+ {
+ await Task.Run(() => FindPortsAsync());
+ }
+ finally
+ {
+ // 버튼 재활성화
+ btFindPort.Enabled = true;
+ }
+ }
+
+ private void FindPortsAsync()
+ {
+ // AGV와 BMS의 응답 포트를 병렬로 동시에 찾는다
+ addLog("===== 포트 자동 검색 시작 (병렬 모드) =====");
+
+ string[] ports = SerialPort.GetPortNames();
+ if (ports.Length == 0)
+ {
+ addLog("사용 가능한 포트가 없습니다");
+ return;
+ }
+
+ addLog($"총 {ports.Length}개 포트 검색 중...");
+
+ // 병렬 검색을 위한 변수
+ object lockObj = new object();
+ string foundAGVPort = null;
+ string foundBMSPort = null;
+ int agvBaud = 115200;
+ int bmsBaud = 9600;
+ bool bothFound = false;
+
+ // 현재 설정된 포트 (우선순위 표시용)
+ string currentAGVPort = null;
+ string currentBMSPort = null;
+
+ this.Invoke(new Action(() =>
+ {
+ currentAGVPort = cmbPortAGV.SelectedItem?.ToString();
+ currentBMSPort = cmbPortBMS.SelectedItem?.ToString();
+ }));
+
+ // 현재 설정 확인 로그
+ addLog($"현재 AGV 설정: {(string.IsNullOrEmpty(currentAGVPort) ? "(선택 안됨)" : currentAGVPort)}");
+ addLog($"현재 BMS 설정: {(string.IsNullOrEmpty(currentBMSPort) ? "(선택 안됨)" : currentBMSPort)}");
+
+ // === 1단계: 현재 설정된 포트 우선 테스트 ===
+ addLog("--- 1단계: 현재 설정 포트 우선 테스트 ---");
+
+ // AGV 현재 포트 우선 테스트
+ if (!string.IsNullOrEmpty(currentAGVPort) && ports.Contains(currentAGVPort))
+ {
+ addLog($"[우선] AGV 현재 포트 테스트: {currentAGVPort}");
+ for (int retry = 1; retry <= 3; retry++)
+ {
+ if (TestAGVPort(currentAGVPort, agvBaud))
+ {
+ foundAGVPort = currentAGVPort;
+ addLog($"✓ 현재 설정 포트에서 AGV 발견: {currentAGVPort}");
+ break;
+ }
+ if (retry < 3) System.Threading.Thread.Sleep(500);
+ }
+ }
+
+ // BMS 현재 포트 우선 테스트
+ if (!string.IsNullOrEmpty(currentBMSPort) && ports.Contains(currentBMSPort))
+ {
+ addLog($"[우선] BMS 현재 포트 테스트: {currentBMSPort}");
+ for (int retry = 1; retry <= 3; retry++)
+ {
+ if (TestBMSPort(currentBMSPort, bmsBaud))
+ {
+ foundBMSPort = currentBMSPort;
+ addLog($"✓ 현재 설정 포트에서 BMS 발견: {currentBMSPort}");
+ break;
+ }
+ if (retry < 3) System.Threading.Thread.Sleep(500);
+ }
+ }
+
+ // 둘 다 찾았으면 즉시 종료
+ if (foundAGVPort != null && foundBMSPort != null)
+ {
+ addLog("현재 설정 포트에서 모두 발견! 검색 종료");
+ ApplyFoundPorts(foundAGVPort, foundBMSPort, agvBaud, bmsBaud);
+ addLog("===== 포트 자동 검색 완료 =====");
+ return;
+ }
+
+ // === 2단계: 나머지 포트 병렬 검색 ===
+ if (foundAGVPort == null || foundBMSPort == null)
+ {
+ addLog("--- 2단계: 나머지 포트 병렬 검색 ---");
+ }
+
+ // 모든 포트를 병렬로 테스트 (단, 이미 테스트한 포트 제외)
+ List tasks = new List();
+
+ foreach (string portName in ports)
+ {
+ string port = portName; // 클로저 캡처용
+
+ // 이미 우선 테스트에서 체크한 포트는 제외
+ if (port == currentAGVPort || port == currentBMSPort)
+ continue;
+
+ Task task = Task.Run(() =>
+ {
+ // 이미 둘 다 찾았으면 즉시 종료
+ if (bothFound) return;
+
+ addLog($"[{port}] 검사 시작...");
+
+ // AGV 테스트 (3회 재시도)
+ for (int retry = 1; retry <= 3; retry++)
+ {
+ // 첫 번째 시도에서는 조기 종료하지 않고 전체 대기 시간 보장
+ if (retry > 1 && bothFound) return;
+
+ try
+ {
+ if (foundAGVPort == null && TestAGVPort(port, agvBaud))
+ {
+ lock (lockObj)
+ {
+ if (foundAGVPort == null)
+ {
+ foundAGVPort = port;
+ addLog($"[{port}] ✓ AGV 발견! (재시도: {retry}/3)");
+
+ // 둘 다 찾았는지 확인
+ if (foundBMSPort != null)
+ {
+ bothFound = true;
+ addLog("AGV와 BMS 모두 발견! 검색 종료");
+ }
+ }
+ }
+ break;
+ }
+ }
+ catch { }
+
+ if (retry < 3 && foundAGVPort == null && !bothFound)
+ {
+ System.Threading.Thread.Sleep(500);
+ }
+ }
+
+ // BMS 테스트 (3회 재시도)
+ for (int retry = 1; retry <= 3; retry++)
+ {
+ // 첫 번째 시도에서는 조기 종료하지 않고 전체 대기 시간 보장
+ if (retry > 1 && bothFound) return;
+
+ try
+ {
+ if (foundBMSPort == null && TestBMSPort(port, bmsBaud))
+ {
+ lock (lockObj)
+ {
+ if (foundBMSPort == null)
+ {
+ foundBMSPort = port;
+ addLog($"[{port}] ✓ BMS 발견! (재시도: {retry}/3)");
+
+ // 둘 다 찾았는지 확인
+ if (foundAGVPort != null)
+ {
+ bothFound = true;
+ addLog("AGV와 BMS 모두 발견! 검색 종료");
+ }
+ }
+ }
+ break;
+ }
+ }
+ catch { }
+
+ if (retry < 3 && foundBMSPort == null && !bothFound)
+ {
+ System.Threading.Thread.Sleep(500);
+ }
+ }
+
+ // 둘 다 실패한 경우에만 로그
+ if (!bothFound && foundAGVPort != port && foundBMSPort != port)
+ {
+ addLog($"[{port}] ✗ 응답 없음");
+ }
+ });
+
+ tasks.Add(task);
+ }
+
+ // 나머지 포트가 있을 경우만 대기
+ if (tasks.Count > 0)
+ {
+ addLog("나머지 포트 테스트 진행 중...");
+
+ // 주기적으로 체크하면서 둘 다 찾았으면 즉시 종료
+ while (!Task.WaitAll(tasks.ToArray(), 100))
+ {
+ if (bothFound)
+ {
+ addLog("검색 조기 종료 - 모든 포트 발견");
+ break;
+ }
+ }
+ }
+
+ // 결과 적용
+ ApplyFoundPorts(foundAGVPort, foundBMSPort, agvBaud, bmsBaud);
+ addLog("===== 포트 자동 검색 완료 =====");
+ }
+
+ ///
+ /// 발견된 포트를 UI에 적용
+ ///
+ private void ApplyFoundPorts(string agvPort, string bmsPort, int agvBaud, int bmsBaud)
+ {
+ if (agvPort != null)
+ {
+ this.Invoke(new Action(() =>
+ {
+ int idx = cmbPortAGV.Items.IndexOf(agvPort);
+ if (idx >= 0)
+ {
+ cmbPortAGV.SelectedIndex = idx;
+ tbBaudAGV.Text = agvBaud.ToString();
+ }
+ }));
+ addLog($"✓ AGV 포트: {agvPort} (Baud: {agvBaud})");
+ }
+ else
+ {
+ addLog("⚠ AGV 포트를 찾지 못했습니다");
+ }
+
+ if (bmsPort != null)
+ {
+ this.Invoke(new Action(() =>
+ {
+ int idx = cmbPortBMS.Items.IndexOf(bmsPort);
+ if (idx >= 0)
+ {
+ cmbPortBMS.SelectedIndex = idx;
+ tbBaudBMS.Text = bmsBaud.ToString();
+ }
+ }));
+ addLog($"✓ BMS 포트: {bmsPort} (Baud: {bmsBaud})");
+ }
+ else
+ {
+ addLog("⚠ BMS 포트를 찾지 못했습니다");
+ }
+ }
+
+ ///
+ /// AGV 포트인지 테스트 (btTestAGV_Click 코드 사용)
+ ///
+ private bool TestAGVPort(string portName, int baudRate)
+ {
+ try
+ {
+ using (arDev.Narumi port = new arDev.Narumi())
+ {
+ port.PortName = portName;
+ port.BaudRate = baudRate;
+
+ addLog($" >> AGV 테스트 시작: {portName} @ {baudRate}bps");
+
+ port.Open();
+ addLog($" >> AGV 포트 열기 성공: {portName}");
+
+ DateTime sendtime = DateTime.Now;
+ port.AGVMoveStop("test");
+ addLog($" >> AGV 명령 전송: {portName} (시간: {sendtime:HH:mm:ss.fff})");
+
+ // 응답 대기 (더 길게, 주기적으로 체크)
+ int maxWait = 2000; // 최대 2초 대기
+ int waitStep = 100;
+ int totalWait = 0;
+
+ while (totalWait < maxWait)
+ {
+ System.Threading.Thread.Sleep(waitStep);
+ totalWait += waitStep;
+
+ // lastRecvTime 확인
+ if (port.lastRecvTime > sendtime)
+ {
+ addLog($" >> ✓✓✓ AGV 응답 수신: {portName} (응답시간: {port.lastRecvTime:HH:mm:ss.fff}, 대기: {totalWait}ms)");
+ addLog($" >> 응답 데이터: {port.LastRecvString}");
+ port.Close();
+ return true;
+ }
+
+ // LastRecvString도 확인 (lastRecvTime이 업데이트 안 될 수도 있음)
+ if (!string.IsNullOrEmpty(port.LastRecvString))
+ {
+ addLog($" >> ✓✓✓ AGV 응답 감지 (문자열): {portName} (대기: {totalWait}ms)");
+ addLog($" >> 응답 데이터: {port.LastRecvString}");
+ port.Close();
+ return true;
+ }
+ }
+
+ addLog($" >> AGV 응답 없음: {portName} (최종 대기: {totalWait}ms, 마지막수신: {port.lastRecvTime:HH:mm:ss.fff})");
+ port.Close();
+ }
+ }
+ catch (Exception ex)
+ {
+ addLog($" >> AGV 테스트 오류: {portName} - {ex.Message}");
+ }
+
+ return false;
+ }
+
+ ///
+ /// BMS 포트인지 테스트 (btTestBMS_Click 코드 사용)
+ ///
+ private bool TestBMSPort(string portName, int baudRate)
+ {
+ try
+ {
+ using (SerialPort port = new SerialPort())
+ {
+ port.PortName = portName;
+ port.BaudRate = baudRate;
+ port.DataBits = 8;
+ port.Parity = Parity.None;
+ port.StopBits = StopBits.One;
+ port.ReadTimeout = 500;
+ port.WriteTimeout = 500;
+
+ addLog($" >> BMS 테스트 시작: {portName} @ {baudRate}bps");
+
+ port.Open();
+ addLog($" >> BMS 포트 열기 성공: {portName}");
+
+ // BMS 테스트 명령 전송 (테스트 버튼과 동일한 명령)
+ var testCmd = new List();
+ testCmd.Add(0xDD);
+ testCmd.Add(0xA5);
+ testCmd.Add(0x03);
+ testCmd.Add(0x00);
+ testCmd.Add(0xFF);
+ testCmd.Add(0xFD);
+ testCmd.Add(0x77);
+ testCmd.Add(0x0D);
+
+ port.Write(testCmd.ToArray(), 0, testCmd.Count);
+ addLog($" >> BMS 명령 전송: {portName} - {BitConverter.ToString(testCmd.ToArray())}");
+
+ // 응답 대기
+ System.Threading.Thread.Sleep(300);
+
+ if (port.BytesToRead > 0)
+ {
+ byte[] response = new byte[port.BytesToRead];
+ port.Read(response, 0, response.Length);
+
+ addLog($" >> BMS 응답 수신: {portName} ({response.Length} bytes) - {BitConverter.ToString(response)}");
+
+ // BMS 프로토콜 확인 (STX=0xDD, ETX=0x77)
+ if (response.Length >= 2 && response[0] == 0xDD && response[response.Length - 1] == 0x77)
+ {
+ addLog($" >> ✓✓✓ BMS 프로토콜 확인: {portName}");
+ port.Close();
+ return true;
+ }
+ else
+ {
+ addLog($" >> BMS 프로토콜 불일치: {portName}");
+ }
+ }
+ else
+ {
+ addLog($" >> BMS 응답 없음: {portName}");
+ }
+
+ port.Close();
+ }
+ }
+ catch (Exception ex)
+ {
+ addLog($" >> BMS 테스트 오류: {portName} - {ex.Message}");
+ }
+
+ return false;
+ }
+
+ private void toolStripButton1_Click(object sender, EventArgs e)
+ {
+ LoadSetting();
+ }
+
+ private void toolStripButton2_Click(object sender, EventArgs e)
+ {
+ SaveSetting();
+ }
+ }
+}
diff --git a/Cs_HMI/TestProject/Test_Port/Form1.resx b/Cs_HMI/TestProject/Test_Port/Form1.resx
new file mode 100644
index 0000000..f2d1ecf
--- /dev/null
+++ b/Cs_HMI/TestProject/Test_Port/Form1.resx
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 17, 17
+
+
+
+
+ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+ YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK
+ YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X
+ /aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t
+ I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM
+ cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh
+ 6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD
+ lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A
+ HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb
+ 1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC
+ nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
+
+
+
+
+ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+ YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK
+ YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X
+ /aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t
+ I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM
+ cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh
+ 6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD
+ lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A
+ HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb
+ 1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC
+ nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
+
+
+
\ No newline at end of file
diff --git a/Cs_HMI/TestProject/Test_Port/PUB.cs b/Cs_HMI/TestProject/Test_Port/PUB.cs
new file mode 100644
index 0000000..c41b0c9
--- /dev/null
+++ b/Cs_HMI/TestProject/Test_Port/PUB.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Project
+{
+ public static class PUB
+ {
+ public static AR.Log log;
+ public static Project.CSetting setting;
+ public static void init()
+ {
+ log = new AR.Log();
+ setting = new Project.CSetting();
+ setting.Load();
+ }
+ }
+}
diff --git a/Cs_HMI/TestProject/Test_Port/Program.cs b/Cs_HMI/TestProject/Test_Port/Program.cs
new file mode 100644
index 0000000..e46c518
--- /dev/null
+++ b/Cs_HMI/TestProject/Test_Port/Program.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace Project
+{
+ static class Program
+ {
+ ///
+ /// 해당 응용 프로그램의 주 진입점입니다.
+ ///
+ [STAThread]
+ static void Main()
+ {
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run(new Form1());
+ }
+ }
+}
diff --git a/Cs_HMI/TestProject/Test_Port/Properties/AssemblyInfo.cs b/Cs_HMI/TestProject/Test_Port/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..6321a6f
--- /dev/null
+++ b/Cs_HMI/TestProject/Test_Port/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해
+// 제어됩니다. 어셈블리와 관련된 정보를 수정하려면
+// 이러한 특성 값을 변경하세요.
+[assembly: AssemblyTitle("Test_Port")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Test_Port")]
+[assembly: AssemblyCopyright("Copyright © 2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에
+// 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면
+// 해당 형식에 대해 ComVisible 특성을 true로 설정하세요.
+[assembly: ComVisible(false)]
+
+// 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다.
+[assembly: Guid("ccfa2ce7-a539-4adc-b803-f759284c3463")]
+
+// 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다.
+//
+// 주 버전
+// 부 버전
+// 빌드 번호
+// 수정 버전
+//
+// 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호가 자동으로
+// 지정되도록 할 수 있습니다.
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Cs_HMI/TestProject/Test_Port/Properties/Resources.Designer.cs b/Cs_HMI/TestProject/Test_Port/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..b6d7817
--- /dev/null
+++ b/Cs_HMI/TestProject/Test_Port/Properties/Resources.Designer.cs
@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+//
+// 이 코드는 도구를 사용하여 생성되었습니다.
+// 런타임 버전:4.0.30319.42000
+//
+// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
+// 이러한 변경 내용이 손실됩니다.
+//
+//------------------------------------------------------------------------------
+
+namespace Project.Properties {
+ using System;
+
+
+ ///
+ /// 지역화된 문자열 등을 찾기 위한 강력한 형식의 리소스 클래스입니다.
+ ///
+ // 이 클래스는 ResGen 또는 Visual Studio와 같은 도구를 통해 StronglyTypedResourceBuilder
+ // 클래스에서 자동으로 생성되었습니다.
+ // 멤버를 추가하거나 제거하려면 .ResX 파일을 편집한 다음 /str 옵션을 사용하여 ResGen을
+ // 다시 실행하거나 VS 프로젝트를 다시 빌드하십시오.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// 이 클래스에서 사용하는 캐시된 ResourceManager 인스턴스를 반환합니다.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Project.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// 이 강력한 형식의 리소스 클래스를 사용하여 모든 리소스 조회에 대한 현재 스레드의 CurrentUICulture
+ /// 속성을 재정의합니다.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/Cs_HMI/TestProject/Test_Port/Properties/Resources.resx b/Cs_HMI/TestProject/Test_Port/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/Cs_HMI/TestProject/Test_Port/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Cs_HMI/TestProject/Test_Port/Properties/Settings.Designer.cs b/Cs_HMI/TestProject/Test_Port/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..5264ba8
--- /dev/null
+++ b/Cs_HMI/TestProject/Test_Port/Properties/Settings.Designer.cs
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+//
+// 이 코드는 도구를 사용하여 생성되었습니다.
+// 런타임 버전:4.0.30319.42000
+//
+// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
+// 이러한 변경 내용이 손실됩니다.
+//
+//------------------------------------------------------------------------------
+
+namespace Project.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/Cs_HMI/TestProject/Test_Port/Properties/Settings.settings b/Cs_HMI/TestProject/Test_Port/Properties/Settings.settings
new file mode 100644
index 0000000..3964565
--- /dev/null
+++ b/Cs_HMI/TestProject/Test_Port/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/Cs_HMI/TestProject/Test_Port/Test_Port.csproj b/Cs_HMI/TestProject/Test_Port/Test_Port.csproj
new file mode 100644
index 0000000..f32cbcb
--- /dev/null
+++ b/Cs_HMI/TestProject/Test_Port/Test_Port.csproj
@@ -0,0 +1,103 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {CCFA2CE7-A539-4ADC-B803-F759284C3463}
+ WinExe
+ Project
+ Test_Port
+ v4.8
+ 512
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ ..\..\..\..\..\..\Amkor\AGV4\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\..\DLL\arCommUtil.dll
+
+
+ ..\..\DLL\arControl.Net4.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Form
+
+
+ Form1.cs
+
+
+
+
+
+ Form1.cs
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+ Designer
+
+
+ True
+ Resources.resx
+ True
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+ True
+ Settings.settings
+ True
+
+
+
+
+
+
+
+ {8bae0eac-3d25-402f-9a65-2ba1ecfe28b7}
+ NARUMI
+
+
+ {14e8c9a5-013e-49ba-b435-efefc77dd623}
+ CommData
+
+
+
+
\ No newline at end of file