diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs index c6d81be..d094aee 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs @@ -119,6 +119,9 @@ namespace AGVNavigationCore.Controls var worldPoint = ScreenToWorld(e.Location); var hitNode = GetItemAt(worldPoint); + // 가동 모드에서는 더블클릭 편집 방지 + if (_canvasMode == CanvasMode.Run) return; + if (hitNode == null) return; if (hitNode.Type == NodeType.Normal) diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs index 4555ef3..798e760 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs @@ -39,7 +39,8 @@ namespace AGVNavigationCore.Controls { Edit, // 편집 가능 (맵 에디터) Sync, // 동기화 모드 (장비 설정 동기화) - Emulator // 에뮬레이터 모드 + Emulator, // 에뮬레이터 모드 + Run // 가동 모드 (User Request) } /// diff --git a/Cs_HMI/Project/StateMachine/_SPS.cs b/Cs_HMI/Project/StateMachine/_SPS.cs index e16910f..4663dbd 100644 --- a/Cs_HMI/Project/StateMachine/_SPS.cs +++ b/Cs_HMI/Project/StateMachine/_SPS.cs @@ -207,7 +207,7 @@ namespace Project //연결은 되었으나 통신이 지난지 10초가 지났다면 자동종료한다 var tsRecv = VAR.TIME.RUN(recvtime); var tsConn = VAR.TIME.RUN(conntry); - if (tsRecv.TotalSeconds > 10 && tsConn.TotalSeconds > 5) + if (tsRecv.TotalSeconds > 30 && tsConn.TotalSeconds > 5) { this.BeginInvoke(new Action(() => { diff --git a/Cs_HMI/Project/ViewForm/fAuto.cs b/Cs_HMI/Project/ViewForm/fAuto.cs index 4e9d3c9..609abbe 100644 --- a/Cs_HMI/Project/ViewForm/fAuto.cs +++ b/Cs_HMI/Project/ViewForm/fAuto.cs @@ -43,11 +43,19 @@ namespace Project.ViewForm private void OnNodeSelected(object sender, NodeBase node, MouseEventArgs e) { - if (e.Button != MouseButtons.Right) return; if (node == null) return; var mapnode = node as MapNode; if (mapnode == null) return; + // [Run Mode] Left Click: AGV Operation + if (PUB._mapCanvas.Mode == AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Run && e.Button == MouseButtons.Left) + { + HandleRunModeClick(mapnode); + return; + } + + if (e.Button != MouseButtons.Right) return; + // 도킹 가능한 노드인지 또는 작업 노드인지 확인 if (mapnode.isDockingNode == false) return; @@ -158,6 +166,9 @@ namespace Project.ViewForm //} this.timer1.Start(); + + // Set Run Mode + PUB._mapCanvas.Mode = AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Run; } private void AGV_DataReceive(object sender, arDev.Narumi.DataEventArgs e) { @@ -194,6 +205,9 @@ namespace Project.ViewForm PUB.sm.StepChanged -= Sm_StepChanged; this.ctlAuto1.ButtonClick -= CtlAuto1_ButtonClick; PUB.AGV.DataReceive -= AGV_DataReceive; + + // Reset Mode to Edit + PUB._mapCanvas.Mode = AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Edit; } bool tmrun = false; @@ -201,7 +215,15 @@ namespace Project.ViewForm private void fAuto_VisibleChanged(object sender, EventArgs e) { this.timer1.Enabled = this.Visible; - if (timer1.Enabled) timer1.Start(); + if (timer1.Enabled) + { + timer1.Start(); + // 화면이 보일 때 Run 모드로 강제 설정 + if (PUB._mapCanvas.Mode != AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Run) + { + PUB._mapCanvas.Mode = AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Run; + } + } else timer1.Stop(); } @@ -236,5 +258,49 @@ namespace Project.ViewForm //tmrun = false; } + + private void HandleRunModeClick(MapNode targetNode) + { + if (targetNode == null) return; + + ENIGProtocol.AGVCommandHE targetCmd = ENIGProtocol.AGVCommandHE.Goto; + string confirmMsg = ""; + + if (targetNode.StationType == StationType.Charger) + { + if (MessageBox.Show($"[{targetNode.Id}] 충전기로 이동하여 충전을 진행하시겠습니까?", "작업 확인", MessageBoxButtons.YesNo) == DialogResult.Yes) + { + targetCmd = ENIGProtocol.AGVCommandHE.Charger; + ExecuteManualCommand(targetNode, targetCmd); + } + return; + } + else if (targetNode.isDockingNode) + { + // Loader, Unloader, Buffer, Cleaner - Pick/Drop Selection + ContextMenuStrip menu = new ContextMenuStrip(); + + var pickOn = new ToolStripMenuItem("Pick On (Move & Pick)"); + pickOn.Click += (s, args) => ExecuteManualCommand(targetNode, ENIGProtocol.AGVCommandHE.PickOn); + menu.Items.Add(pickOn); + + var pickOff = new ToolStripMenuItem("Pick Off (Move & Drop)"); + pickOff.Click += (s, args) => ExecuteManualCommand(targetNode, ENIGProtocol.AGVCommandHE.PickOff); + menu.Items.Add(pickOff); + + menu.Show(Cursor.Position); + return; + } + else + { + // Normal Node + if (MessageBox.Show($"[{targetNode.Id}] 노드로 이동하시겠습니까?", "이동 확인", MessageBoxButtons.YesNo) == DialogResult.Yes) + { + targetCmd = ENIGProtocol.AGVCommandHE.Goto; + ExecuteManualCommand(targetNode, targetCmd); + } + return; + } + } } } diff --git a/Cs_HMI/SubProject/AGV/Narumi.cs b/Cs_HMI/SubProject/AGV/Narumi.cs index 825cf58..48d588f 100644 --- a/Cs_HMI/SubProject/AGV/Narumi.cs +++ b/Cs_HMI/SubProject/AGV/Narumi.cs @@ -229,7 +229,7 @@ namespace arDev private void RevSTS(Dataframe frame) { LastSTS = frame.DataString; - string rcvdNow = frame.DataString; + string rcvdNow = frame.DataString.Replace("\0",""); byte[] bRcvData = frame.Buffer; var encoding = System.Text.Encoding.Default; try @@ -267,8 +267,12 @@ namespace arDev nDataTemp = Convert.ToByte(rcvdNow.Substring(idx, 2), 16); signal1.SetValue(nDataTemp); idx += 2; - nDataTemp = Convert.ToByte(rcvdNow.Substring(idx, 2), 16); - signal2.SetValue(nDataTemp); + if(idx <= rcvdNow.Length-2) + { + nDataTemp = Convert.ToByte(rcvdNow.Substring(idx, 2), 16); + signal2.SetValue(nDataTemp); + } + DataReceive?.Invoke(this, new DataEventArgs(DataType.STS)); diff --git a/Cs_HMI/SubProject/AGV/NarumiSerialComm.cs b/Cs_HMI/SubProject/AGV/NarumiSerialComm.cs index 7725af9..23f26a9 100644 --- a/Cs_HMI/SubProject/AGV/NarumiSerialComm.cs +++ b/Cs_HMI/SubProject/AGV/NarumiSerialComm.cs @@ -51,6 +51,12 @@ namespace arDev public string WriteErrorMessage = string.Empty; public int WaitTimeout { get; set; } = 1000; public int MinRecvLength { get; set; } = 1; + + // Polling Thread related + protected Thread _recvThread; + protected volatile bool _isReading = false; + + /// /// 포트이름 /// @@ -103,7 +109,7 @@ namespace arDev _device = new System.IO.Ports.SerialPort(); this.BaudRate = 9600; ScanInterval = 10; - _device.DataReceived += barcode_DataReceived; + // _device.DataReceived += barcode_DataReceived; // Removed event handler _device.ErrorReceived += this.barcode_ErrorReceived; _device.WriteTimeout = 3000; _device.ReadTimeout = 3000; @@ -147,9 +153,23 @@ namespace arDev // } - _device.DataReceived -= barcode_DataReceived; + // Stop reading thread + _isReading = false; + + // _device.DataReceived -= barcode_DataReceived; // Removed event handler _device.ErrorReceived -= this.barcode_ErrorReceived; + if (_recvThread != null && _recvThread.IsAlive) + { + _recvThread.Join(500); + } + + if (_device != null) + { + if (_device.IsOpen) _device.Close(); + _device.Dispose(); + } + // Free any unmanaged objects here. // disposed = true; @@ -159,8 +179,24 @@ namespace arDev { try { - _device.Open(); - return IsOpen; + if (_device.IsOpen == false) + { + _device.Open(); + } + + if (_device.IsOpen) + { + // Start polling thread + if (_isReading == false) + { + _isReading = true; + _recvThread = new Thread(ReadPort); + _recvThread.IsBackground = true; + _recvThread.Start(); + } + return true; + } + return false; } catch (Exception ex) { @@ -193,14 +229,31 @@ namespace arDev public virtual bool Close() { - if (_device != null && _device.IsOpen) + try { - _device.DiscardInBuffer(); - _device.DiscardOutBuffer(); - _device.Close(); //dispose에서는 포트를 직접 클리어하지 않게 해뒀다. - return true; + _isReading = false; // Stop thread loop + + if (_recvThread != null && _recvThread.IsAlive) + { + if (!_recvThread.Join(500)) // Wait for thread to finish + { + // _recvThread.Abort(); // Avoid Abort if possible + } + } + + if (_device != null && _device.IsOpen) + { + _device.DiscardInBuffer(); + _device.DiscardOutBuffer(); + _device.Close(); //dispose에서는 포트를 직접 클리어하지 않게 해뒀다. + return true; + } + else return false; + } + catch (Exception) + { + return false; } - else return false; } protected Boolean RaiseRecvData() { @@ -226,6 +279,9 @@ namespace arDev try { + // UI update might need Invoke if this event handler updates UI directly, + // but usually the subscriber handles Invoke. + // Since we are running on a background thread now, subscribers must be aware. Message?.Invoke(this, new MessageEventArgs(Data, true)); //recvmessage if (ProcessRecvData(Data) == false) { @@ -262,9 +318,12 @@ namespace arDev } byte[] buffer = new byte[] { }; + + // Replaced with ReadPort Loop + /* void barcode_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e) { - + try { int ReadCount = _device.BytesToRead; @@ -308,6 +367,62 @@ namespace arDev } } + */ + + void ReadPort() + { + while (_isReading) + { + try + { + if (_device == null || !_device.IsOpen) + { + Thread.Sleep(100); + continue; + } + + int readCount = _device.BytesToRead; + if (readCount > 0) + { + byte[] buffer = new byte[readCount]; + _device.Read(buffer, 0, buffer.Length); + + byte[] remainBuffer; + Repeat: + if (CustomParser(buffer, out remainBuffer)) + { + //분석완료이므로 받은 데이터를 버퍼에 기록한다 + if (LastReceiveBuffer == null || (LastReceiveBuffer.Length != tempBuffer.Count)) + Array.Resize(ref LastReceiveBuffer, tempBuffer.Count); + Array.Copy(tempBuffer.ToArray(), LastReceiveBuffer, tempBuffer.Count); + tempBuffer.Clear(); + + //수신메세지발생 + RaiseRecvData(); + if (remainBuffer != null && remainBuffer.Length > 0) + { + //버퍼를 변경해서 다시 전송을 해준다. + buffer = new byte[remainBuffer.Length]; // Reallocate buffer for remaining data + Array.Copy(remainBuffer, buffer, remainBuffer.Length); + goto Repeat; //남은 버퍼가 있다면 진행을 해준다. + } + } + } + else + { + Thread.Sleep(20); // Data 없음, 대기 + } + } + catch (Exception ex) + { + // Thread 상에서 Exception 발생 시 로그 남기고 계속 진행 여부 결정 + // 여기서는 에러 메시지 발생시키고 Sleep + ErrorMessage = ex.Message; + this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true)); + Thread.Sleep(1000); + } + } + } #endregion diff --git a/Cs_HMI/SubProject/CommUtil b/Cs_HMI/SubProject/CommUtil index b070b71..ed05439 160000 --- a/Cs_HMI/SubProject/CommUtil +++ b/Cs_HMI/SubProject/CommUtil @@ -1 +1 @@ -Subproject commit b070b711f0167eb7a90f443bd860fb5c16efd5bd +Subproject commit ed05439991fdddba2d7123b7a89a89245bcd044c diff --git a/Document/teset.pptx b/Document/teset.pptx deleted file mode 100644 index 03fb514..0000000 Binary files a/Document/teset.pptx and /dev/null differ diff --git a/Document/~$PICkit 프로그램 다운로드 매뉴얼.pptx b/Document/~$PICkit 프로그램 다운로드 매뉴얼.pptx deleted file mode 100644 index c805eb5..0000000 Binary files a/Document/~$PICkit 프로그램 다운로드 매뉴얼.pptx and /dev/null differ diff --git a/Document/RE_ _이노텍_ 통신 프로토콜 송부건 (AGV_V350_LF)-pic다운로드메뉴얼.msg b/Document/이메일/RE_ _이노텍_ 통신 프로토콜 송부건 (AGV_V350_LF)-pic다운로드메뉴얼.msg similarity index 100% rename from Document/RE_ _이노텍_ 통신 프로토콜 송부건 (AGV_V350_LF)-pic다운로드메뉴얼.msg rename to Document/이메일/RE_ _이노텍_ 통신 프로토콜 송부건 (AGV_V350_LF)-pic다운로드메뉴얼.msg diff --git a/Document/RE_ _이노텍_ 통신 프로토콜 송부건 (AGV_V350_LF)-프토토콜+펌웨어파일.msg b/Document/이메일/RE_ _이노텍_ 통신 프로토콜 송부건 (AGV_V350_LF)-프토토콜+펌웨어파일.msg similarity index 100% rename from Document/RE_ _이노텍_ 통신 프로토콜 송부건 (AGV_V350_LF)-프토토콜+펌웨어파일.msg rename to Document/이메일/RE_ _이노텍_ 통신 프로토콜 송부건 (AGV_V350_LF)-프토토콜+펌웨어파일.msg diff --git a/Document/_이노텍_ RFID 헥사파일 송부건.msg b/Document/이메일/_이노텍_ RFID 헥사파일 송부건.msg similarity index 100% rename from Document/_이노텍_ RFID 헥사파일 송부건.msg rename to Document/이메일/_이노텍_ RFID 헥사파일 송부건.msg diff --git a/Document/_이노텍_ 리프트형 AGV 펌웨어 송부건.msg b/Document/이메일/_이노텍_ 리프트형 AGV 펌웨어 송부건.msg similarity index 100% rename from Document/_이노텍_ 리프트형 AGV 펌웨어 송부건.msg rename to Document/이메일/_이노텍_ 리프트형 AGV 펌웨어 송부건.msg diff --git a/Document/_이노텍_ 통신 프로토콜 송부건 (AGV_V350_LF)-프로토콜.msg b/Document/이메일/_이노텍_ 통신 프로토콜 송부건 (AGV_V350_LF)-프로토콜.msg similarity index 100% rename from Document/_이노텍_ 통신 프로토콜 송부건 (AGV_V350_LF)-프로토콜.msg rename to Document/이메일/_이노텍_ 통신 프로토콜 송부건 (AGV_V350_LF)-프로토콜.msg diff --git a/Document/리듐인산철 통신프로토콜정리 .xlsx b/Document/통신프로토콜/리듐인산철 통신프로토콜정리 .xlsx similarity index 100% rename from Document/리듐인산철 통신프로토콜정리 .xlsx rename to Document/통신프로토콜/리듐인산철 통신프로토콜정리 .xlsx diff --git a/Document/통신 프로토콜_AGV_V350_LF_24.11.20.xlsx b/Document/통신프로토콜/통신 프로토콜_AGV_V350_LF_24.11.20.xlsx similarity index 100% rename from Document/통신 프로토콜_AGV_V350_LF_24.11.20.xlsx rename to Document/통신프로토콜/통신 프로토콜_AGV_V350_LF_24.11.20.xlsx diff --git a/Document/통신 프로토콜_AGV_V350_LF_25.01.10.xlsx b/Document/통신프로토콜/통신 프로토콜_AGV_V350_LF_25.01.10.xlsx similarity index 100% rename from Document/통신 프로토콜_AGV_V350_LF_25.01.10.xlsx rename to Document/통신프로토콜/통신 프로토콜_AGV_V350_LF_25.01.10.xlsx diff --git a/Document/통신 프로토콜_AGV_V350_LF_25.01.10_r2.xlsx b/Document/통신프로토콜/통신 프로토콜_AGV_V350_LF_25.01.10_r2.xlsx similarity index 100% rename from Document/통신 프로토콜_AGV_V350_LF_25.01.10_r2.xlsx rename to Document/통신프로토콜/통신 프로토콜_AGV_V350_LF_25.01.10_r2.xlsx