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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 + 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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 - 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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 + 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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 + 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