From 1f378713369dae3bff327d9ed7430e0a4a96d367 Mon Sep 17 00:00:00 2001 From: backuppc Date: Wed, 17 Dec 2025 14:54:33 +0900 Subject: [PATCH] .. --- Cs_HMI/Data/NewMap.json | 2 +- Cs_HMI/Project/Device/Xbee.cs | 34 +- Cs_HMI/Project/StateMachine/Step/_SM_RUN.cs | 13 +- .../StateMachine/Step/_SM_RUN_CHGOFF.cs | 4 +- .../Project/StateMachine/Step/_SM_RUN_GOTO.cs | 304 +------ .../StateMachine/Step/_SM_RUN_POSCHK.cs | 7 +- Cs_HMI/Project/StateMachine/_AGV.cs | 8 +- Cs_HMI/Project/StateMachine/_Loop.cs | 20 +- Cs_HMI/Project/StateMachine/_SPS.cs | 124 ++- Cs_HMI/Project/StateMachine/_Xbee.cs | 68 +- Cs_HMI/StateMachine/StateMachine.cs | 800 +++++++++--------- Cs_HMI/SubProject/AGV/Command.cs | 6 +- Cs_HMI/SubProject/AGV/DataEventArgs.cs | 2 +- Cs_HMI/SubProject/AGV/Dataframe.cs | 69 ++ Cs_HMI/SubProject/AGV/NARUMI.csproj | 2 + Cs_HMI/SubProject/AGV/Narumi.cs | 69 +- Cs_HMI/SubProject/AGV/NarumiSerialComm.cs | 494 +++++++++++ Cs_HMI/SubProject/CommData/CommData.csproj | 1 + Cs_HMI/SubProject/CommData/ISerialComm.cs | 18 + Cs_HMI/SubProject/CommData/RS232.cs | 27 +- Cs_HMI/SubProject/EnigProtocol | 2 +- Cs_HMI/TestProject/Test_ACS/MainForm.cs | 28 +- 22 files changed, 1218 insertions(+), 884 deletions(-) create mode 100644 Cs_HMI/SubProject/AGV/Dataframe.cs create mode 100644 Cs_HMI/SubProject/AGV/NarumiSerialComm.cs create mode 100644 Cs_HMI/SubProject/CommData/ISerialComm.cs diff --git a/Cs_HMI/Data/NewMap.json b/Cs_HMI/Data/NewMap.json index 2e25694..2a8a225 100644 --- a/Cs_HMI/Data/NewMap.json +++ b/Cs_HMI/Data/NewMap.json @@ -1214,6 +1214,6 @@ "BackgroundColorArgb": -14671840, "ShowGrid": false }, - "CreatedDate": "2025-12-16T10:58:08.630Z", + "CreatedDate": "2025-12-17T04:23:31.655Z", "Version": "1.3" } \ No newline at end of file diff --git a/Cs_HMI/Project/Device/Xbee.cs b/Cs_HMI/Project/Device/Xbee.cs index 67e72fd..1bee693 100644 --- a/Cs_HMI/Project/Device/Xbee.cs +++ b/Cs_HMI/Project/Device/Xbee.cs @@ -14,11 +14,11 @@ using System.Windows.Forms; namespace Project.Device { - public class Xbee : SerialPort + public class Xbee : SerialPort, arDev.ISerialComm { public string buffer = string.Empty; public System.Text.StringBuilder newbuffer = new StringBuilder(); - public string errorMessage = string.Empty; + public string ErrorMessage { get; set; } = string.Empty; public DateTime LastStatusSendTime { get; set; } = DateTime.Now; private EEProtocol proto; @@ -67,11 +67,23 @@ namespace Project.Device } catch (Exception ex) { - errorMessage = ex.Message; + ErrorMessage = ex.Message; return false; } } + public new bool Close() + { + try + { + base.Close(); + return true; + } + catch + { + return false; + } + } public new bool Open() { try @@ -81,8 +93,8 @@ namespace Project.Device } catch (Exception ex) { - errorMessage = ex.Message; - PUB.logxbee.AddE(errorMessage); + ErrorMessage = ex.Message; + PUB.logxbee.AddE(ErrorMessage); return false; } } @@ -145,10 +157,12 @@ namespace Project.Device byte cmd = (byte)ENIGProtocol.AGVCommandEH.Error; if (errormessage.Length > 30) errormessage = errormessage.Substring(0, 29); - var data = new byte[] { (byte)errcode }; + var data = new List(); + data.Add((byte)errcode); var datamsg = System.Text.Encoding.Default.GetBytes(errormessage); + data.AddRange(datamsg); - var packet = proto.CreatePacket(id, cmd, data); + var packet = proto.CreatePacket(id, cmd, data.ToArray()); Send(packet); } @@ -173,7 +187,7 @@ namespace Project.Device public void SendStatus() { if (this.IsOpen == false) return; - if ( sendlock.WaitOne() == false) return; + if (sendlock.WaitOne() == false) return; sendlock.Reset(); /* @@ -257,8 +271,8 @@ namespace Project.Device } catch (Exception ex) { - errorMessage = ex.Message; - PUB.logxbee.AddE(errorMessage); + ErrorMessage = ex.Message; + PUB.logxbee.AddE(ErrorMessage); } finally { diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN.cs index a9c7625..e7c0fe9 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN.cs @@ -28,7 +28,7 @@ namespace Project } //가동불가 조건 확인 - if (CheckStopCondition() == false) return; + if (CheckStopCondition() == false) return; //중단기능이 동작이라면 처리하지 않는다. if (PUB.sm.bPause) @@ -109,10 +109,17 @@ namespace Project var target = PUB._virtualAGV.TargetNode; PUB.log.Add($"목적지({target.RfidId}) 도착완료 타입:{target.Type}, 출발지:{PUB._virtualAGV.StartNode.RfidId}"); - switch(target.StationType) + switch (target.StationType) { case AGVNavigationCore.Models.StationType.Buffer: //현재위치가 마지막경로의 NODEID와 일치해야한다 + if (PUB._virtualAGV.CurrentPath == null) + { + PUB.log.AddAT("목적지 버퍼이동완료 했지만 상세경로가 없습니다"); + PUB.XBE.BufferInComplete = false; + PUB.XBE.BufferOutComplete = false; + break; + } var lastPath = PUB._virtualAGV.CurrentPath.DetailedPath.LastOrDefault(); if (lastPath.NodeId.Equals(PUB._virtualAGV.CurrentNode.Id)) { @@ -131,7 +138,7 @@ namespace Project PUB.XBE.BufferOutComplete = false; break; } - + PUB._virtualAGV.Turn = AGVNavigationCore.Models.AGVTurn.None; PUB.sm.SetNewRunStep(ERunStep.READY); } diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_CHGOFF.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_CHGOFF.cs index 575d16f..98a34a1 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_CHGOFF.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_CHGOFF.cs @@ -54,8 +54,8 @@ namespace Project // 1분 타임아웃 체크 if (stepTime.TotalMinutes >= 1) { - PUB.XBE.errorMessage = $"충전해제가 실패되었습니다(1분)"; - PUB.log.AddE(PUB.XBE.errorMessage); + PUB.XBE.ErrorMessage = $"충전해제가 실패되었습니다(1분)"; + PUB.log.AddE(PUB.XBE.ErrorMessage); PUB.sm.SetNewStep(eSMStep.IDLE); return false; } diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOTO.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOTO.cs index bc763dd..6f39bed 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOTO.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOTO.cs @@ -17,6 +17,7 @@ namespace Project { ///명령어 재전송 간격(기본 2초) var CommandInterval = 2; + var funcName = "_SM_RUN_GOTO"; //충전 상태가 OFF되어야 동작하게한다 if (_SM_RUN_CHGOFF(isFirst, stepTime) == false) @@ -29,269 +30,48 @@ namespace Project VAR.TIME.Update(eVarTime.CheckGotoTargetSet); } - //PUB._virtualAGV. + //라이더멈춤이 설정되어있다면 음성으로 알려준다 200409 + if (PUB.AGV.system1.stop_by_front_detect == true) + { + var tsSpeak = DateTime.Now - LastSpeakTime; + if (tsSpeak.TotalSeconds >= PUB.setting.alarmSoundTerm) + { + PUB.Speak(Lang.전방에물체가감지되었습니다); + LastSpeakTime = DateTime.Now; + } + return false; + } - //목적지가 설정되었는지 체크한다. - //Z if (PUB.mapctl.Manager.agv.TargetRFID.IsEmpty) - // { - // //최대 5초간 설정여부를 확인하고 - // if (VAR.TIME.RUN(eVarTime.CheckGotoTargetSet).TotalSeconds > 5) - // { - // //실패시에는 READY로 전환한다. - // PUB.sm.SetNewRunStep(ERunStep.READY); - // PUB.Speak(Lang.목적지가없어대기상태로전환합니다); - // } - // return false; - // } - - //var idx = 1; - //var BeforePredictIdx = -1; - //var predict = PUB.mapctl.Manager.PredictResult; - //if (PUB.sm.RunStepSeq == idx++) - //{ - // PUB.Speak(Lang.위치로이동합니다); - // PUB.log.Add($"목적지 위치 이동시작({PUB.mapctl.Manager.agv.TargetRFID.Value})"); - // VAR.TIME.Update(eVarTime.CheckGotoTargetSet); - // VAR.TIME.Set(eVarTime.SendGotoCommand, DateTime.Now.AddDays(-1)); - // PUB.sm.UpdateRunStepSeq(); - // return false; - //} - //else if (PUB.sm.RunStepSeq == idx++) - //{ - // //멈춰야하는경우 - // if (predict.MoveState == AGVControl.AGVMoveState.Stop) - // { - // if (PUB.AGV.system1.agv_run) - // { - // if (VAR.TIME.RUN(eVarTime.SendGotoCommand).TotalSeconds > 2) - // { - // PUB.Speak("AGV Stop"); - // PUB.AGV.AGVMoveStop("Predict", arDev.Narumi.eStopOpt.Stop); - // VAR.TIME.Update(eVarTime.SendGotoCommand); - // } - // return false; - // } - // else - // { - // //완료되었거나 턴을진행해야한다 - // if (predict.ReasonCode == AGVControl.AGVActionReasonCode.Arrived || - // predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnMove || - // predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnPoint) - // { - // GotoTurnStep = 0; - // GotoTurnSetTime = DateTime.Now.AddDays(-1); - // PUB.sm.UpdateRunStepSeq(); - // } - // return false; - // } - // } - // else //이동해야하는 경우 - // { - // //속도와 방향이 불일치하는 경우 다시 설정한다 (속도: H,L,M,[S] - // AGVControl.AgvDir AGV_Direction = (AGVControl.AgvDir)PUB.AGV.data.Direction; - // AGVControl.AgvSpeed AGV_Speed = (AGVControl.AgvSpeed)PUB.AGV.data.Speed; - // AGVControl.AgvSts AGV_Sts = (AGVControl.AgvSts)PUB.AGV.data.Sts; - - // //상태값이 바뀌었다면 전송을 해야한다 - // if (predict.Direction != AGV_Direction || predict.MoveSpeed != AGV_Speed || predict.MoveDiv != AGV_Sts) - // { - // if (VAR.TIME.RUN(eVarTime.SendGotoCommand).TotalSeconds > CommandInterval) - // { - // arDev.Narumi.eBunki v_bunki = arDev.Narumi.eBunki.Strate; - // if (predict.MoveDiv == AGVControl.AgvSts.Straight) v_bunki = arDev.Narumi.eBunki.Strate; - // else if (predict.MoveDiv == AGVControl.AgvSts.Left) v_bunki = arDev.Narumi.eBunki.Left; - // else if (predict.MoveDiv == AGVControl.AgvSts.Right) v_bunki = arDev.Narumi.eBunki.Right; - - // arDev.Narumi.eMoveDir v_dir = arDev.Narumi.eMoveDir.Backward; - // if (predict.Direction == AGVControl.AgvDir.Forward) v_dir = arDev.Narumi.eMoveDir.Forward; - // else if (predict.Direction == AGVControl.AgvDir.Backward) v_dir = arDev.Narumi.eMoveDir.Backward; - - // arDev.Narumi.eMoveSpd v_spd = arDev.Narumi.eMoveSpd.Low; - // if (predict.MoveSpeed == AGVControl.AgvSpeed.Middle) v_spd = arDev.Narumi.eMoveSpd.Middle; - // else if (predict.MoveSpeed == AGVControl.AgvSpeed.High) v_spd = arDev.Narumi.eMoveSpd.High; - // else if (predict.MoveSpeed == AGVControl.AgvSpeed.Low) v_spd = arDev.Narumi.eMoveSpd.Low; - - // //이동셋팅을 해준다 - // PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData - // { - // Bunki = v_bunki, - // Direction = v_dir, - // PBSSensor = 1, - // Speed = v_spd, - // }); - - // if (predict.MoveSpeed == AGVControl.AgvSpeed.MarkStop) - // { - // PUB.AGV.AGVMoveStop("Predict", arDev.Narumi.eStopOpt.Stop); - // } - // VAR.TIME.Update(eVarTime.SendGotoCommand); - // } - // return false; - // } - - // //정지상태라면 이동 명령을 전달한다 - // if (PUB.AGV.system1.agv_run == false) - // { - // if (VAR.TIME.RUN(eVarTime.SendGotoCommand).TotalSeconds > CommandInterval) - // { - // PUB.Speak("AGV Start"); - - // arDev.Narumi.eRunOpt v_dir = arDev.Narumi.eRunOpt.Backward; - // if (predict.Direction == AGVControl.AgvDir.Forward) v_dir = arDev.Narumi.eRunOpt.Forward; - // else if (predict.Direction == AGVControl.AgvDir.Backward) v_dir = arDev.Narumi.eRunOpt.Backward; - - // PUB.AGV.AGVMoveRun(v_dir); - // VAR.TIME.Update(eVarTime.SendGotoCommand); - // } - // return false; - // } - // } - - // //예측이 업데이트되지 않으면 오류 처리해야한다 - // if (BeforePredictIdx == -1) BeforePredictIdx = (int)predict.Idx; - // else if (BeforePredictIdx != predict.Idx) //이전사용한 IDX와 다르다면 예측이 실행된 경우이다 - // BeforePredictIdx = (int)predict.Idx; - // else - // { - // //5초이상 예측값이 업데이트되지 않으면 오류 처리한다. - // var tsPredict = DateTime.Now - predict.CreateTime; - // if (tsPredict.TotalSeconds > 5) - // { - // PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.PredictFix, Lang.예측값이계산되지않아이동을중단합니다); - // PUB.Speak(Lang.예측값이계산되지않아이동을중단합니다); - // PUB.sm.SetNewRunStep(ERunStep.READY); - // } - // } - - // return false; - //} - //else if (PUB.sm.RunStepSeq == idx++) - //{ - // if (predict.ReasonCode == AGVControl.AGVActionReasonCode.Arrived) - // { - // PUB.Speak(Lang.목적지이동이완료되었습니다); - // PUB.sm.SetNewRunStep(ERunStep.READY); - // PUB.sm.UpdateRunStepSeq(); - // } - // else if (predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnMove || - // predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnPoint) - // { - // //턴을 해야하는 경우이다 - // //좌턴을 기본으로 진행하며, 좌턴이동 후 마크스탑을 입력한다 - // if (GotoTurnStep == 0) - // { - // //턴을 한적이 없으므로 턴을 먼저 진행한다 - // arDev.Narumi.eMoveDir moveDir = arDev.Narumi.eMoveDir.Backward; - // if (predict.Direction == AGVControl.AgvDir.Forward) moveDir = arDev.Narumi.eMoveDir.Forward; - // if (PUB.AGV.data.Sts != 'L' || PUB.AGV.data.Speed != 'L' || PUB.AGV.data.Direction != moveDir.ToString()[0]) - // { - // //셋팅이 다르다면 3초간격으로 전송한다 - // var tsTurnSet = DateTime.Now - GotoTurnSetTime; - // if (tsTurnSet.TotalSeconds > 3) - // { - // PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData - // { - // Bunki = arDev.Narumi.eBunki.Left, - // Direction = moveDir, - // PBSSensor = 1, - // Speed = arDev.Narumi.eMoveSpd.Low, - // }); - // GotoTurnSetTime = DateTime.Now; - // PUB.log.Add("Turn Bunki Set"); - // } - // } - // else - // { - // PUB.mapctl.Manager.agv.CurrentRFID.TurnOK = false; - // PUB.mapctl.Manager.agv.CurrentRFID.TurnStart = DateTime.Now; - // PUB.sm.UpdateRunStepSeq(); //셋팅이 맞으니 다음스텝으로 진행한다 - // GotoTurnStep += 1; - // } - // } - // } - // else PUB.sm.UpdateRunStepSeq(); - // return false; - //} - //else if (PUB.sm.RunStepSeq == idx++) - //{ - // //턴이완료되길 기다린다. - // if (predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnMove || - // predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnPoint) - // { - // //(최소5초는 기다리고 판단한다) - // if (stepTime.TotalSeconds < 5) return false; - - // //최대30초는 기다려준다 - // if (stepTime.TotalSeconds > 30) - // { - // var ermsg = "Turn Timeout(30sec)"; - // PUB.log.AddE(ermsg); - // PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.TurnTimeout, ermsg); - // PUB.sm.SetNewRunStep(ERunStep.READY); - // } - - // //모션이 멈추었다면 턴이완료된것이다. - // if (PUB.AGV.system1.agv_stop) - // { - // if (PUB.AGV.system1.Mark1_check == false && PUB.AGV.system1.Mark2_check == false) - // { - // PUB.log.AddE($"Turn 완료이나 Mark 센서가 확인되지 않았습니다"); - // } - // GotoTurnStep += 1; - // PUB.sm.UpdateRunStepSeq(); - // } - // else - // { - // //아직 이동중이므로 대기한다 - // } - // } - // else PUB.sm.UpdateRunStepSeq(); //기타사항은 다음으로 넘어간다 - // return false; - //} - //else if (PUB.sm.RunStepSeq == idx++) - //{ - // if (predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnMove || - // predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnPoint) - // { - // if (GotoTurnStep < 2) - // { - // PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.TurnError, "턴시퀀스 완료 실패"); - // PUB.log.AddE($"턴완료시퀀스가 2가아닙니다. 대기 상태로 강제 전환합니다"); - // PUB.sm.SetNewRunStep(ERunStep.READY); - // } - // else - // { - // PUB.log.AddI("Turn Complete"); - // } - - // //방향전환용 턴이라면 이동기록을 추가해서 방향이 맞도록 처리해주자 - // if (predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnMove) - // { - // var rfid = PUB.mapctl.Manager.agv.CurrentRFID; - // var lastHistory = PUB.mapctl.Manager.agv.MovementHistory.Last(); - // //원래방향에서 반대로 처리한다 - // var revDir = lastHistory.Direction == AGVControl.AgvDir.Backward ? AGVControl.AgvDir.Forward : AGVControl.AgvDir.Backward; - // PUB.mapctl.Manager.agv.AddToMovementHistory(rfid.Value, rfid.Location, revDir); - // } - // else - // { - // //이동용 RFID에서 턴명령이 들어있는경우였다 - // PUB.mapctl.Manager.agv.CurrentRFID.TurnEnd = DateTime.Now; - // PUB.mapctl.Manager.agv.CurrentRFID.TurnOK = true; - // } - - // PUB.sm.UpdateRunStepSeq(); - // } - // else PUB.sm.UpdateRunStepSeq(); - // return false; - //} - - - //좌턴이동명령 전송 - - //마크스탑전송 - - //마크스탑이 확인되면 나머지는 경로예측에 맡긴다 + var idx = 1; + if (PUB.sm.RunStepSeq == idx++) + { + if(PUB._virtualAGV.TargetNode == null) + { + PUB.log.Add($"대상노드가 없어 이동을할 수 없습니다"); + PUB.sm.SetNewRunStep(ERunStep.READY); + return false; + } + PUB.sm.UpdateRunStepSeq(); + return false; + } + else if (PUB.sm.RunStepSeq == idx++) + { + //모션 전후진 제어 + if (UpdateMotionPositionForMark(funcName)) + { + PUB.AGV.AGVMoveStop(funcName); + PUB.sm.UpdateRunStepSeq(); + } + return false; + } + else if (PUB.sm.RunStepSeq == idx++) + { + //QC까지 모두 완료되었다.(완전히 정차할때까지 기다린다) + PUB.Speak(Lang.홈검색완료, true); + PUB.AddEEDB($"홈검색완료({PUB.Result.TargetPos})"); + PUB.sm.UpdateRunStepSeq(); + return false; + } return true; } diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_POSCHK.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_POSCHK.cs index 65293d2..e7ae26e 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_POSCHK.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_POSCHK.cs @@ -13,8 +13,11 @@ namespace Project public Boolean _SM_RUN_POSCHK(bool isFirst, TimeSpan stepTime) { //현재위치가 설정되어있는지 확인한다, 현재위치값이 있는 경우 True 를 반환 - var currentnode = PUB.FindByNodeID(PUB._virtualAGV.CurrentNode.Id); - if (currentnode != null) return true; + if (PUB._virtualAGV.CurrentNode != null && PUB._virtualAGV.PrevNode != null) + return true; + + //최소2개의 노드정보가 있어야 진행가능하므로 prevNode 값이 있는지 확인한다. + //이동을 하지 않고있다면 전진을 진행한다 if (PUB.AGV.system1.agv_run == false) diff --git a/Cs_HMI/Project/StateMachine/_AGV.cs b/Cs_HMI/Project/StateMachine/_AGV.cs index f7e081d..9667ecf 100644 --- a/Cs_HMI/Project/StateMachine/_AGV.cs +++ b/Cs_HMI/Project/StateMachine/_AGV.cs @@ -18,16 +18,16 @@ namespace Project { private void AGV_Message(object sender, arDev.Narumi.MessageEventArgs e) { - if (e.MsgType == arDev.arRS232.MessageType.Normal) + if (e.MsgType == arDev.NarumiSerialComm.MessageType.Normal) PUB.logagv.AddE(e.Message); - else if (e.MsgType == arDev.arRS232.MessageType.Normal) + else if (e.MsgType == arDev.NarumiSerialComm.MessageType.Normal) PUB.logagv.Add(e.Message); - else if (e.MsgType == arDev.arRS232.MessageType.Recv) + else if (e.MsgType == arDev.NarumiSerialComm.MessageType.Recv) { if (e.Message.Substring(1).StartsWith("STS") == false) PUB.logagv.Add("AGV-RX", e.Message); } - else if (e.MsgType == arDev.arRS232.MessageType.Send) + else if (e.MsgType == arDev.NarumiSerialComm.MessageType.Send) PUB.logagv.Add("AGV-TX", e.Message); else { diff --git a/Cs_HMI/Project/StateMachine/_Loop.cs b/Cs_HMI/Project/StateMachine/_Loop.cs index 0bee820..ac9de15 100644 --- a/Cs_HMI/Project/StateMachine/_Loop.cs +++ b/Cs_HMI/Project/StateMachine/_Loop.cs @@ -76,7 +76,8 @@ namespace Project } } - + string lockstep = string.Empty; + System.Threading.ManualResetEvent mreloop = new System.Threading.ManualResetEvent(true); void sm_Running(object sender, StateMachine.StateMachine.RunningEventArgs e) { @@ -89,6 +90,11 @@ namespace Project } else PUB.sm.WaitFirstRun = false; + if (mreloop.WaitOne(1) == false) return; + mreloop.Reset(); + + lockstep = e.Step.ToString(); + //main loop switch (e.Step) { @@ -186,11 +192,14 @@ namespace Project break; case eSMStep.SYNC: - if(e.isFirst) + if (e.isFirst) { // 동기화 완료 시 캔버스 모드 복귀 - if (PUB._mapCanvas != null) - PUB._mapCanvas.SetSyncStatus("설정 동기화", 0f, "환경설정 값으로 AGV컨트롤러를 설정 합니다"); + this.Invoke(new Action(() => { + if (PUB._mapCanvas != null) + PUB._mapCanvas.SetSyncStatus("설정 동기화", 0f, "환경설정 값으로 AGV컨트롤러를 설정 합니다"); + })); + } if (_SM_RUN_SYNC(runStepisFirst, PUB.sm.GetRunSteptime)) { @@ -205,7 +214,6 @@ namespace Project PUB.Speak( Lang.초기화완료); PUB.sm.SetNewStep(eSMStep.IDLE); - return; } break; @@ -269,6 +277,8 @@ namespace Project } break; } + + mreloop.Set(); } void DeleteFile(string path) diff --git a/Cs_HMI/Project/StateMachine/_SPS.cs b/Cs_HMI/Project/StateMachine/_SPS.cs index 9ba6c5b..e16910f 100644 --- a/Cs_HMI/Project/StateMachine/_SPS.cs +++ b/Cs_HMI/Project/StateMachine/_SPS.cs @@ -23,6 +23,7 @@ namespace Project DateTime agvsendstarttime = DateTime.Now; DateTime lastXbeStatusSendTime = DateTime.Now; DateTime lastBmsQueryTime = DateTime.Now; + object connectobj = new object(); void sm_SPS(object sender, EventArgs e) { @@ -36,41 +37,54 @@ namespace Project // ========== 1. 장치 연결 관리 ========== // AGV 연결 - ConnectSerialPort(PUB.AGV, PUB.setting.Port_AGV, PUB.setting.Baud_AGV, - eVarTime.LastConn_AGV, eVarTime.LastConnTry_AGV, eVarTime.LastRecv_AGV); + lock (connectobj) + { + ConnectSerialPort(PUB.AGV, PUB.setting.Port_AGV, PUB.setting.Baud_AGV, + eVarTime.LastConn_AGV, eVarTime.LastConnTry_AGV, eVarTime.LastRecv_AGV); + } + // XBee 연결 - ConnectSerialPort(PUB.XBE, PUB.setting.Port_XBE, PUB.setting.Baud_XBE, - eVarTime.LastConn_XBE, eVarTime.LastConnTry_XBE, eVarTime.LastRecv_XBE); + lock (connectobj) + { + ConnectSerialPort(PUB.XBE, PUB.setting.Port_XBE, PUB.setting.Baud_XBE, + eVarTime.LastConn_XBE, eVarTime.LastConnTry_XBE, eVarTime.LastRecv_XBE); + } + // BMS 연결 - if (PUB.BMS.IsOpen == false) + lock (connectobj) { - var ts = VAR.TIME.RUN(eVarTime.LastConn_BAT); - if (ts.TotalSeconds > 3) + if (PUB.BMS.IsOpen == false) { - PUB.log.Add($"BMS 연결 시도: {PUB.setting.Port_BAT}"); - PUB.BMS.PortName = PUB.setting.Port_BAT; - if (PUB.BMS.Open()) - PUB.log.AddI($"BMS 연결 완료({PUB.setting.Port_BAT})"); + var ts = VAR.TIME.RUN(eVarTime.LastConn_BAT); + if (ts.TotalSeconds > 3) + { + PUB.log.Add($"BMS 연결 시도: {PUB.setting.Port_BAT}"); + PUB.BMS.PortName = PUB.setting.Port_BAT; + if (PUB.BMS.Open()) + PUB.log.AddI($"BMS 연결 완료({PUB.setting.Port_BAT})"); - VAR.TIME.Update(eVarTime.LastConn_BAT); - VAR.TIME.Update(eVarTime.LastConnTry_BAT); + VAR.TIME.Update(eVarTime.LastConn_BAT); + VAR.TIME.Update(eVarTime.LastConnTry_BAT); + } } - } - else if (PUB.BMS.IsValid == false) - { - var ts = VAR.TIME.RUN(eVarTime.LastConnTry_BAT); - if (ts.TotalSeconds > (PUB.setting.interval_bms * 2.5)) + else if (PUB.BMS.IsValid == false) { - this.BeginInvoke(new Action(() => { - PUB.log.Add("BMS 자동 연결 해제 (응답 없음)"); - PUB.BMS.Close(); - })); - VAR.TIME.Set(eVarTime.LastConn_BAT, DateTime.Now.AddSeconds(5)); + var ts = VAR.TIME.RUN(eVarTime.LastConnTry_BAT); + if (ts.TotalSeconds > (PUB.setting.interval_bms * 2.5)) + { + this.BeginInvoke(new Action(() => + { + PUB.log.Add("BMS 자동 연결 해제 (응답 없음)"); + PUB.BMS.Close(); + })); + VAR.TIME.Set(eVarTime.LastConn_BAT, DateTime.Now.AddSeconds(5)); + } } } + // ========== 2. XBee 상태 전송 ========== if (PUB.XBE != null && PUB.XBE.IsOpen) { @@ -133,7 +147,7 @@ namespace Project /// /// 시리얼 포트 연결 (arDev.arRS232) /// - bool ConnectSerialPort(arDev.arRS232 dev, string port, int baud, eVarTime conn, eVarTime conntry, eVarTime recvtime) + bool ConnectSerialPort(arDev.ISerialComm dev, string port, int baud, eVarTime conn, eVarTime conntry, eVarTime recvtime) { if (port.isEmpty()) return false; @@ -151,7 +165,8 @@ namespace Project PUB.log.Add($"Connect to {port}:{baud}"); if (dev.Open()) { - PUB.log.Add(port, $"[AGV:{port}:{baud}] 연결 완료"); + VAR.TIME[recvtime] = DateTime.Now; //값을 수신한것처럼한다 + PUB.log.Add(port, $"[{port}:{baud}] 연결 완료"); } else { @@ -163,7 +178,7 @@ namespace Project } else { - var errmessage = dev.errorMessage; + var errmessage = dev.ErrorMessage; PUB.log.AddE($"[AGV:{port}:{baud}] {errmessage}"); } } @@ -177,12 +192,14 @@ namespace Project } } else if (dev.PortName.Equals(port) == false) - { - this.BeginInvoke(new Action(() => { + { + this.BeginInvoke(new Action(() => + { PUB.log.Add(port, $"포트 변경({dev.PortName}->{port})으로 연결 종료"); + VAR.TIME.Set(conntry, DateTime.Now); dev.Close(); })); - + VAR.TIME.Update(conntry); } else if (dev.IsOpen) @@ -191,8 +208,9 @@ namespace Project var tsRecv = VAR.TIME.RUN(recvtime); var tsConn = VAR.TIME.RUN(conntry); if (tsRecv.TotalSeconds > 10 && tsConn.TotalSeconds > 5) - { - this.BeginInvoke(new Action(() => { + { + this.BeginInvoke(new Action(() => + { PUB.log.Add($"{port} 자동 연결 해제 (응답 없음)"); dev.Close(); })); @@ -202,49 +220,7 @@ namespace Project return true; } - /// - /// 시리얼 포트 연결 (Device.Xbee) - /// - void ConnectSerialPort(Device.Xbee dev, string port, int baud, eVarTime conn, eVarTime conntry, eVarTime recvtime) - { - if (dev.IsOpen == false && port.isEmpty() == false) - { - var tsPLC = VAR.TIME.RUN(conntry); - if (tsPLC.TotalSeconds > 5) - { - VAR.TIME.Update(conntry); - try - { - VAR.TIME.Update(recvtime); - dev.PortName = port; - dev.BaudRate = baud; - if (dev.Open()) - { - PUB.log.Add(port, $"[XBEE:{port}:{baud}] 연결 완료"); - } - else - { - var errmessage = dev.errorMessage; - PUB.log.AddE($"[XBEE:{port}:{baud}] {errmessage}"); - } - VAR.TIME.Update(conn); - VAR.TIME.Update(conntry); - } - catch (Exception ex) - { - PUB.log.AddE(ex.Message); - } - } - } - else if (dev.PortName.Equals(port) == false) - { - this.BeginInvoke(new Action(() => { - PUB.log.Add(port, $"포트 변경({dev.PortName}->{port})으로 연결 종료"); - dev.Close(); - })); - VAR.TIME[(int)conntry] = DateTime.Now; - } - } + } } diff --git a/Cs_HMI/Project/StateMachine/_Xbee.cs b/Cs_HMI/Project/StateMachine/_Xbee.cs index ce73ca2..00504e7 100644 --- a/Cs_HMI/Project/StateMachine/_Xbee.cs +++ b/Cs_HMI/Project/StateMachine/_Xbee.cs @@ -111,20 +111,32 @@ namespace Project } break; + case ENIGProtocol.AGVCommandHE.GotoAlias: case ENIGProtocol.AGVCommandHE.Goto: //move to tag - if (data.Length > 4) + var datalength = cmd == ENIGProtocol.AGVCommandHE.GotoAlias ? 1 : 4; + if (data.Length > datalength) { - var currTag = System.Text.Encoding.Default.GetString(data, 1, data.Length - 1); - if(ushort.TryParse(currTag, out ushort currtagvalue)) + var currTag = System.Text.Encoding.Default.GetString(data, 1, data.Length - 1).Trim(); + MapNode targetNode = null; + if(cmd == ENIGProtocol.AGVCommandHE.GotoAlias) { - var targetNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == currtagvalue); - + targetNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.AliasName == currTag); + } + else + { + if (ushort.TryParse(currTag, out ushort currtagvalue)) + targetNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == currtagvalue); + else PUB.log.Add($"targstring 이 숫자가 아니라서 대상을 설정할 수 없습니다 값:{currTag}"); + } + if (targetNode != null) + { //자동상태가아니라면 처리하지 않는다. if (VAR.BOOL[eVarBool.FLAG_AUTORUN] == false) { PUB.log.AddE($"[{logPrefix}-Goto] 자동실행상태가 아닙니다"); PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.ManualMode, $"{currTag}"); + return; } //목적지 @@ -143,33 +155,21 @@ namespace Project { PUB.log.AddE($"[{logPrefix}-Goto] 시작노드가 없습니다(현재위치 없음) NodeID:{PUB._virtualAGV.CurrentNode.Id}"); } - - if (startNode != null) - { - //시작위치가 존재한다면 경로를 예측한다. - var rltGoto = CalcPath(startNode, targetNode); - if (rltGoto.result == null) - { - PUB.log.AddE($"[{logPrefix}-Goto] 경로예측실패 {rltGoto.message}"); - } - else - { - //경로예측을 화면에 표시해준다. - PUB._virtualAGV.SetPath(rltGoto.result); - var pathWithRfid = rltGoto.result.GetSimplePath().Select(nodeId => PUB._virtualAGV.GetRfidByNodeId(PUB._mapCanvas.Nodes, nodeId)).ToList(); - PUB.log.Add($"경로예측결과:{pathWithRfid}"); - } - } - + //대상이동으로 처리한다. - PUB.sm.SetNewRunStep(StateMachine.ERunStep.GOTO); + if(PUB.sm.RunStep != ERunStep.GOTO) + { + PUB.sm.SetNewRunStep(StateMachine.ERunStep.GOTO); + PUB.sm.ResetRunStepSeq(); + } + //Move to - PUB.log.Add($"[{logPrefix}-Goto] {startNode.RfidId} -> {targetNode.RfidId}"); + PUB.log.Add($"[{logPrefix}-{cmd}] {startNode.RfidId} -> {targetNode.RfidId}"); } - else PUB.log.AddE($"[{logPrefix}-Goto] TagString Value Error:{data}"); + else PUB.log.AddE($"[{logPrefix}-{cmd}] 대상노드가 없습니다 {data}"); } - else PUB.log.AddE($"[{logPrefix}-Goto] TagString Lenght Error:{data.Length}"); + else PUB.log.AddE($"[{logPrefix}-{cmd}] Length Error:{data.Length}"); break; case ENIGProtocol.AGVCommandHE.Stop: //stop @@ -253,7 +253,11 @@ namespace Project PUB.log.AddI($"충전을 시작합니다"); } break; - + + default: + PUB.logagv.AddE($"Unknown Command : {cmd}"); + PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.UnknownCommand, $"{cmd}"); + break; } } } @@ -291,7 +295,7 @@ namespace Project _simulatorCanvas.FitToNodes(); string Message = string.Empty; - if (advancedResult.Success) + if (advancedResult != null && advancedResult.Success) { // 도킹 검증이 없는 경우 추가 검증 수행 if (advancedResult.DockingValidation == null || !advancedResult.DockingValidation.IsValidationRequired) @@ -311,12 +315,12 @@ namespace Project //UpdateAdvancedPathDebugInfo(advancedResult); } - else + else if(advancedResult != null) { // 경로 실패시 디버깅 정보 초기화 - //_pathDebugLabel.Text = $"경로: 실패 - {advancedResult.ErrorMessage}"; - advancedResult = null; + //_pathDebugLabel.Text = $"경로: 실패 - {advancedResult.ErrorMessage}"; Message = $"경로를 찾을 수 없습니다:\n{advancedResult.ErrorMessage}"; + advancedResult = null; } return (advancedResult, Message); diff --git a/Cs_HMI/StateMachine/StateMachine.cs b/Cs_HMI/StateMachine/StateMachine.cs index 60bbfe2..414afbd 100644 --- a/Cs_HMI/StateMachine/StateMachine.cs +++ b/Cs_HMI/StateMachine/StateMachine.cs @@ -5,425 +5,429 @@ using System.Text; namespace Project.StateMachine { - public partial class StateMachine : IDisposable - { - public DateTime UpdateTime; - public TimeSpan RunSpeed = new TimeSpan(0); - private Boolean bLoop = true; - private DateTime StepStartTime; - private byte _messageOption; + public partial class StateMachine : IDisposable + { + public DateTime UpdateTime; + public TimeSpan RunSpeed = new TimeSpan(0); + private Boolean bLoop = true; + private DateTime StepStartTime; + private byte _messageOption; - private System.Threading.Thread worker; - private Boolean firstRun = false; + private System.Threading.Thread worker; + private Boolean firstRun = false; - public Boolean WaitFirstRun = false; + public Boolean WaitFirstRun = false; - public StateMachine() - { - WaitFirstRun = false; - UpdateTime = DateTime.Now; - _messageOption = 0xFF; //모든메세지가 오도록 한다. - worker = new System.Threading.Thread(new System.Threading.ThreadStart(Loop)) - { - IsBackground = false - }; - StepStartTime = DateTime.Parse("1982-11-23"); - } + public StateMachine() + { + WaitFirstRun = false; + UpdateTime = DateTime.Now; + _messageOption = 0xFF; //모든메세지가 오도록 한다. + worker = new System.Threading.Thread(new System.Threading.ThreadStart(Loop)) + { + IsBackground = false + }; + StepStartTime = DateTime.Parse("1982-11-23"); + } - ~StateMachine() - { - Dispose(false); - } + ~StateMachine() + { + Dispose(false); + } - #region "Dispose" + #region "Dispose" - private bool disposed = false; - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - // Check to see if Dispose has already been called. - if (!this.disposed) - { - if (disposing) - { - // Dispose managed resources. - } - - Stop(); - // unmanaged resources here. - // If disposing is false, - // only the following code is executed. - disposed = true; - } - } - - #endregion - - public void Stop() - { - if(bLoop != false) bLoop = false; - //if (worker.IsAlive) - // if (worker.Join(1000) == false) - // worker.Abort(); - } - public void Start() - { - if (worker.ThreadState == System.Threading.ThreadState.Stopped) - { - worker = new System.Threading.Thread(new System.Threading.ThreadStart(Loop)) - { - IsBackground = false - }; - } - bLoop = true; - worker.Start(); - } - - private Boolean _isthreadrun; - public Boolean IsThreadRun { get { return _isthreadrun; } } - public int LoopCount { get { return _loopCount; } } - public DateTime LastLoopTime { get { return _lastLoopTime; } } - - private int _loopCount = 0; - private DateTime _lastLoopTime = DateTime.Now; - - void Loop() - { - _isthreadrun = true; - if (GetMsgOpt(EMsgOpt.NORMAL)) RaiseMessage("SM", "Start"); - - try - { - while (bLoop) - { - _loopCount++; - _lastLoopTime = DateTime.Now; - - // 루프 동작 확인용 로그 (10초마다) - if (_loopCount % 10000 == 0) - { - RaiseMessage("SM-LOOP", $"Loop alive - Count:{_loopCount}, bLoop:{bLoop}, Step:{Step}"); - } - - RunSpeed = DateTime.Now - UpdateTime; //이전업데이트에서 현재 시간의 오차 한바퀴 시간이 표시됨 - UpdateTime = DateTime.Now; - - //항상 작동하는 경우 - try - { - var spsHandler = SPS; - if (spsHandler != null) - { - var spsStartTime = DateTime.Now; - var spsTask = System.Threading.Tasks.Task.Run(() => - { - try - { - spsHandler(this, new EventArgs()); - } - catch (Exception ex) - { - RaiseMessage("SM-ERROR-SPS", $"SPS Exception: {ex.Message}\n{ex.StackTrace}"); - } - }); - - // 타임아웃 1초 - if (!spsTask.Wait(1000)) - { - var elapsed = (DateTime.Now - spsStartTime).TotalSeconds; - RaiseMessage("SM-TIMEOUT", $"SPS 이벤트 타임아웃 ({elapsed:F2}초 초과) - Step:{Step}"); - } - } - } - catch (Exception ex) - { - RaiseMessage("SM-ERROR-SPS", $"SPS Exception: {ex.Message}\n{ex.StackTrace}"); - } - - //작동스텝이 변경되었다면 그것을 알림 처리한다. - if (GetNewStep != Step) - { - if (GetMsgOpt(EMsgOpt.STEPCHANGE)) - RaiseMessage("SM-STEP", string.Format("Step Changed {0} >> {1}", Step, GetNewStep)); - - StepApply(); - firstRun = true; - StepStartTime = DateTime.Now; - } - else - { - //팝업메세지의 종료를 기다리는 식에서 문제가 생긴다 - firstRun = WaitFirstRun; - } - - - //동작중에 발생하는 이벤트 - var runningHandler = Running; - if (runningHandler != null) - { - try - { - var runningStartTime = DateTime.Now; - var runningTask = System.Threading.Tasks.Task.Run(() => - { - try - { - runningHandler(this, new RunningEventArgs(Step, firstRun, StepRunTime)); - } - catch (Exception ex) - { - RaiseMessage("SM-ERROR-RUNNING", $"Running Exception: {ex.Message}\n{ex.StackTrace}"); - } - }); - - // 타임아웃 2초 - if (!runningTask.Wait(2000)) - { - var elapsed = (DateTime.Now - runningStartTime).TotalSeconds; - RaiseMessage("SM-TIMEOUT", $"Running 이벤트 타임아웃 ({elapsed:F2}초 초과) - Step:{Step}, FirstRun:{firstRun}"); - } - } - catch (Exception ex) - { - RaiseMessage("SM-ERROR-RUNNING", $"Running Exception: {ex.Message}\n{ex.StackTrace}"); - } - } System.Threading.Thread.Sleep(1); - } - } - catch (Exception ex) - { - RaiseMessage("SM-FATAL", $"Loop Fatal Exception: {ex.Message}\n{ex.StackTrace}"); - } - finally - { - _isthreadrun = false; - if (GetMsgOpt(EMsgOpt.NORMAL)) RaiseMessage("SM", $"Stop - LoopCount:{_loopCount}"); - } - } - - - /// - /// 상태머신의 작동상태 - /// - public eSMStep Step { get { return _step; } } - - /// - /// 상태머신값을 숫자로 반환 합니다. (20이하는 시스템상태이고 그 이상은 사용자 상태) - /// - public byte StepValue - { - get - { - return (byte)this._step; - } - } - - /// - /// 현재 진행 스텝을 강제로 변경합니다. - /// 일반적인 상태변화는 setNewStep 을 사용하세요. - /// - /// - public void SetCurrentStep(ERunStep step) - { - RaiseMessage("SM-STEP", string.Format("강제 스텝 변경 {0}->{1}", _runstepo, step)); - _runstepo = step; - _runstepn = step; - } - - public void SetNewStep(eSMStep newstep_, bool force = false) - { - if (Step != newstep_) - { - if(force) + private bool disposed = false; + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + protected virtual void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if (!this.disposed) + { + if (disposing) { - RaiseMessage("SM-STEP", string.Format("강제스텝전환 {0} -> {1}", Step, _newstep)); - OldStep = _step; - _step = newstep_; - _newstep = newstep_; - - // 비동기로 이벤트 발생 (블로킹 방지) - var handler = StepChanged; - if (handler != null) - { - var args = new StepChangeEventArgs(OldStep, newstep_); - System.Threading.ThreadPool.QueueUserWorkItem(_ => - { - try { handler(this, args); } - catch { /* 이벤트 핸들러 예외 무시 */ } - }); - } - } + // Dispose managed resources. + } + + Stop(); + // unmanaged resources here. + // If disposing is false, + // only the following code is executed. + disposed = true; + } + } + + #endregion + + public void Stop() + { + if (bLoop != false) bLoop = false; + //if (worker.IsAlive) + // if (worker.Join(1000) == false) + // worker.Abort(); + } + public void Start() + { + if (worker.ThreadState == System.Threading.ThreadState.Stopped) + { + worker = new System.Threading.Thread(new System.Threading.ThreadStart(Loop)) + { + IsBackground = false + }; + } + bLoop = true; + worker.Start(); + } + + private Boolean _isthreadrun; + public Boolean IsThreadRun { get { return _isthreadrun; } } + public int LoopCount { get { return _loopCount; } } + public DateTime LastLoopTime { get { return _lastLoopTime; } } + + private int _loopCount = 0; + private DateTime _lastLoopTime = DateTime.Now; + + void Loop() + { + _isthreadrun = true; + if (GetMsgOpt(EMsgOpt.NORMAL)) RaiseMessage("SM", "Start"); + + try + { + while (bLoop) + { + _loopCount++; + _lastLoopTime = DateTime.Now; + + // 루프 동작 확인용 로그 (10초마다) + if (_loopCount % 10000 == 0) + { + RaiseMessage("SM-LOOP", $"Loop alive - Count:{_loopCount}, bLoop:{bLoop}, Step:{Step}"); + } + + RunSpeed = DateTime.Now - UpdateTime; //이전업데이트에서 현재 시간의 오차 한바퀴 시간이 표시됨 + UpdateTime = DateTime.Now; + + //항상 작동하는 경우 + try + { + var spsHandler = SPS; + if (spsHandler != null) + { + var spsStartTime = DateTime.Now; + var spsTask = System.Threading.Tasks.Task.Run(() => + { + try + { + spsHandler(this, new EventArgs()); + } + catch (Exception ex) + { + RaiseMessage("SM-ERROR-SPS", $"SPS Exception: {ex.Message}\n{ex.StackTrace}"); + } + }); + + // 타임아웃 1초 + if (!spsTask.Wait(1000)) + { + var elapsed = (DateTime.Now - spsStartTime).TotalSeconds; + RaiseMessage("SM-TIMEOUT", $"SPS 이벤트 타임아웃 ({elapsed:F2}초 초과) - Step:{Step}"); + } + } + } + catch (Exception ex) + { + RaiseMessage("SM-ERROR-SPS", $"SPS Exception: {ex.Message}\n{ex.StackTrace}"); + } + + //작동스텝이 변경되었다면 그것을 알림 처리한다. + if (GetNewStep != Step) + { + if (GetMsgOpt(EMsgOpt.STEPCHANGE)) + RaiseMessage("SM-STEP", string.Format("Step Changed {0} >> {1}", Step, GetNewStep)); + + StepApply(); + firstRun = true; + StepStartTime = DateTime.Now; + } + else + { + //팝업메세지의 종료를 기다리는 식에서 문제가 생긴다 + firstRun = WaitFirstRun; + } + + + //동작중에 발생하는 이벤트 + var runningHandler = Running; + if (runningHandler != null) + { + try + { + var runningStartTime = DateTime.Now; + var runningTask = System.Threading.Tasks.Task.Run(() => + { + + //try + //{ + runningHandler(this, new RunningEventArgs(Step, firstRun, StepRunTime)); + //} + //catch (Exception ex) + //{ + // RaiseMessage("SM-ERROR-RUNNING", $"Running Exception: {ex.Message}\n{ex.StackTrace}"); + //} + + + }); + + // 타임아웃 2초 + if (!runningTask.Wait(2000)) + { + var elapsed = (DateTime.Now - runningStartTime).TotalSeconds; + RaiseMessage("SM-TIMEOUT", $"Running 이벤트 타임아웃 ({elapsed:F2}초 초과) - Step:{Step}, FirstRun:{firstRun}"); + } + } + catch (Exception ex) + { + RaiseMessage("SM-ERROR-RUNNING", $"Running Exception: {ex.Message}\n{ex.StackTrace}"); + } + } + System.Threading.Thread.Sleep(1); + } + } + catch (Exception ex) + { + RaiseMessage("SM-FATAL", $"Loop Fatal Exception: {ex.Message}\n{ex.StackTrace}"); + } + finally + { + _isthreadrun = false; + if (GetMsgOpt(EMsgOpt.NORMAL)) RaiseMessage("SM", $"Stop - LoopCount:{_loopCount}"); + } + } + + + /// + /// 상태머신의 작동상태 + /// + public eSMStep Step { get { return _step; } } + + /// + /// 상태머신값을 숫자로 반환 합니다. (20이하는 시스템상태이고 그 이상은 사용자 상태) + /// + public byte StepValue + { + get + { + return (byte)this._step; + } + } + + /// + /// 현재 진행 스텝을 강제로 변경합니다. + /// 일반적인 상태변화는 setNewStep 을 사용하세요. + /// + /// + public void SetCurrentStep(ERunStep step) + { + RaiseMessage("SM-STEP", string.Format("강제 스텝 변경 {0}->{1}", _runstepo, step)); + _runstepo = step; + _runstepn = step; + } + + public void SetNewStep(eSMStep newstep_, bool force = false) + { + if (Step != newstep_) + { + if (force) + { + RaiseMessage("SM-STEP", string.Format("강제스텝전환 {0} -> {1}", Step, _newstep)); + OldStep = _step; + _step = newstep_; + _newstep = newstep_; + + // 비동기로 이벤트 발생 (블로킹 방지) + var handler = StepChanged; + if (handler != null) + { + var args = new StepChangeEventArgs(OldStep, newstep_); + System.Threading.ThreadPool.QueueUserWorkItem(_ => + { + try { handler(this, args); } + catch { /* 이벤트 핸들러 예외 무시 */ } + }); + } + } else { - if (_newstep != newstep_) - { - //바뀌도록 예약을 한다. - _newstep = newstep_; - if (GetMsgOpt(EMsgOpt.STEPCHANGE)) - RaiseMessage("SM-STEP", string.Format("Step Reserve {0} -> {1}", Step, _newstep)); - } - else - { - //예약은 되어있는데 아직 바뀐것은 아니다. - if (GetMsgOpt(EMsgOpt.STEPCHANGE)) - RaiseMessage("SM-STEP", string.Format("Step Already Reserve {0} -> {1}", Step, _newstep)); - } - } - - } - } + if (_newstep != newstep_) + { + //바뀌도록 예약을 한다. + _newstep = newstep_; + if (GetMsgOpt(EMsgOpt.STEPCHANGE)) + RaiseMessage("SM-STEP", string.Format("Step Reserve {0} -> {1}", Step, _newstep)); + } + else + { + //예약은 되어있는데 아직 바뀐것은 아니다. + if (GetMsgOpt(EMsgOpt.STEPCHANGE)) + RaiseMessage("SM-STEP", string.Format("Step Already Reserve {0} -> {1}", Step, _newstep)); + } + } - #region "runstep" - private int _runStepSeq = 0; - public int RunStepSeq { get { return _runStepSeq; } } + } + } + + #region "runstep" + private int _runStepSeq = 0; + public int RunStepSeq { get { return _runStepSeq; } } public Stack BackupRunStep = new Stack(); - private DateTime runStepStartTime = DateTime.Parse("1982-11-23"); - private ERunStep _runstepn = ERunStep.READY; - private ERunStep _runstepo = ERunStep.READY; - public ERunStep RunStep { get { return _runstepo; } } - public ERunStep RunStepNew { get { return _runstepn; } } - public void SetNewRunStep(ERunStep newStep) - { - if (_runstepn != newStep) - { - // Pub.log.Add(string.Format("set new run step {0}->{1}", _runstepn, newStep)); - _runstepn = newStep; - } - } - public void ApplyRunStep() - { - if (_runstepn != _runstepo) - { - //Pub.log.Add(string.Format("apply new run step {0}->{1}", _runstepo, _runstepn)); - _runStepSeq = 0; - _runstepo = _runstepn; - UpdateRunStepSeq();// runStepStartTime = DateTime.Now; - } - } - public TimeSpan GetRunSteptime { get { return DateTime.Now - runStepStartTime; } } - public void SetStepSeq(byte value) - { - _runStepSeq = value; - if (GetMsgOpt(EMsgOpt.NORMAL)) - { - if (_runStepSeq != value) //변화가잇는겨웅에만 처리 220628 - RaiseMessage("STEPSEQ", string.Format("Step Sequence Jump {0} to {1}", _runStepSeq, value)); - } + private DateTime runStepStartTime = DateTime.Parse("1982-11-23"); + private ERunStep _runstepn = ERunStep.READY; + private ERunStep _runstepo = ERunStep.READY; + public ERunStep RunStep { get { return _runstepo; } } + public ERunStep RunStepNew { get { return _runstepn; } } + public void SetNewRunStep(ERunStep newStep) + { + if (_runstepn != newStep) + { + // Pub.log.Add(string.Format("set new run step {0}->{1}", _runstepn, newStep)); + _runstepn = newStep; + } + } + public void ApplyRunStep() + { + if (_runstepn != _runstepo) + { + //Pub.log.Add(string.Format("apply new run step {0}->{1}", _runstepo, _runstepn)); + _runStepSeq = 0; + _runstepo = _runstepn; + UpdateRunStepSeq();// runStepStartTime = DateTime.Now; + } + } + public TimeSpan GetRunSteptime { get { return DateTime.Now - runStepStartTime; } } + public void SetStepSeq(byte value) + { + _runStepSeq = value; + if (GetMsgOpt(EMsgOpt.NORMAL)) + { + if (_runStepSeq != value) //변화가잇는겨웅에만 처리 220628 + RaiseMessage("STEPSEQ", string.Format("Step Sequence Jump {0} to {1}", _runStepSeq, value)); + } - } - public void UpdateRunStepStartTime() - { - runStepStartTime = DateTime.Now; - // Pub.log.Add("Update RunStep Start Time"); - } - public void UpdateRunStepSeq(int incvalue = 1) - { - _runStepSeq += incvalue; - UpdateRunStepStartTime(); - // Pub.log.Add(string.Format("스텝({0}) 시퀀스증가 신규값={1}", runStep, _runStepSeq)); - } - public void ResetRunStepSeq() - { - _runStepSeq = 1; - UpdateRunStepStartTime(); - } + } + public void UpdateRunStepStartTime() + { + runStepStartTime = DateTime.Now; + // Pub.log.Add("Update RunStep Start Time"); + } + public void UpdateRunStepSeq(int incvalue = 1) + { + _runStepSeq += incvalue; + UpdateRunStepStartTime(); + // Pub.log.Add(string.Format("스텝({0}) 시퀀스증가 신규값={1}", runStep, _runStepSeq)); + } + public void ResetRunStepSeq() + { + _runStepSeq = 1; + UpdateRunStepStartTime(); + } - /// - /// runstep 시퀀스값을 1로 설정하고 시작시간도 업데이트 합니다 - /// 기본 스텝상태를 READY로 변경 합니다 - /// - public void ClearRunStep() - { - _runStepSeq = 1; - runStepStartTime = DateTime.Now; - _runstepn = ERunStep.READY; - _runstepo = ERunStep.READY; - } - #endregion + /// + /// runstep 시퀀스값을 1로 설정하고 시작시간도 업데이트 합니다 + /// 기본 스텝상태를 READY로 변경 합니다 + /// + public void ClearRunStep() + { + _runStepSeq = 1; + runStepStartTime = DateTime.Now; + _runstepn = ERunStep.READY; + _runstepo = ERunStep.READY; + } + #endregion - public eSMStep GetNewStep - { - get - { - return _newstep; - } - } - private eSMStep _newstep = eSMStep.NOTSET; - public eSMStep OldStep = eSMStep.NOTSET; //171214 - private eSMStep _step; + public eSMStep GetNewStep + { + get + { + return _newstep; + } + } + private eSMStep _newstep = eSMStep.NOTSET; + public eSMStep OldStep = eSMStep.NOTSET; //171214 + private eSMStep _step; - /// - /// newstep 의 값을 step 에 적용합니다. - /// - private void StepApply() - { - var ostep = _step; - OldStep = _step; _step = _newstep; - - // 비동기로 이벤트 발생 (블로킹 방지) - var handler = StepChanged; - if (handler != null) - { - var args = new StepChangeEventArgs(ostep, _step); - System.Threading.ThreadPool.QueueUserWorkItem(_ => - { - try { handler(this, args); } - catch { /* 이벤트 핸들러 예외 무시 */ } - }); - } - } //171214 + /// + /// newstep 의 값을 step 에 적용합니다. + /// + private void StepApply() + { + var ostep = _step; + OldStep = _step; _step = _newstep; - /// - /// 메세지 출력옵션을 변경 합니다. - /// - /// - /// - public void SetMsgOpt(EMsgOpt opt, Boolean value) - { - byte pos = (byte)opt; - if (value) - _messageOption = (byte)(_messageOption | (1 << pos)); - else - _messageOption = (byte)(_messageOption & ~(1 << pos)); - } - public void SetMegOptOn() { _messageOption = 0xFF; } - public void SetMsgOptOff() { _messageOption = 0; } - public Boolean GetMsgOpt(EMsgOpt opt) - { - byte pos = (byte)opt; - return (_messageOption & (1 << pos)) > 0; - } - public TimeSpan StepRunTime - { - get - { - if (IsThreadRun == false || bLoop == false || StepStartTime.Year == 1982) return new TimeSpan(0); - else return DateTime.Now - StepStartTime; - } - } + // 비동기로 이벤트 발생 (블로킹 방지) + var handler = StepChanged; + if (handler != null) + { + var args = new StepChangeEventArgs(ostep, _step); + System.Threading.ThreadPool.QueueUserWorkItem(_ => + { + try { handler(this, args); } + catch { /* 이벤트 핸들러 예외 무시 */ } + }); + } + } //171214 - public Boolean bPause = false; + /// + /// 메세지 출력옵션을 변경 합니다. + /// + /// + /// + public void SetMsgOpt(EMsgOpt opt, Boolean value) + { + byte pos = (byte)opt; + if (value) + _messageOption = (byte)(_messageOption | (1 << pos)); + else + _messageOption = (byte)(_messageOption & ~(1 << pos)); + } + public void SetMegOptOn() { _messageOption = 0xFF; } + public void SetMsgOptOff() { _messageOption = 0; } + public Boolean GetMsgOpt(EMsgOpt opt) + { + byte pos = (byte)opt; + return (_messageOption & (1 << pos)) > 0; + } + public TimeSpan StepRunTime + { + get + { + if (IsThreadRun == false || bLoop == false || StepStartTime.Year == 1982) return new TimeSpan(0); + else return DateTime.Now - StepStartTime; + } + } - /// - /// 상태머신이 실제 동작중인지 확인합니다. - /// 검사상태 : Step = Run, IsThreadRun, bPause = false - /// - public Boolean IsRun - { - get - { - if (Step == eSMStep.RUN && _isthreadrun && bPause == false) return true; - else return false; - } - } - } + public Boolean bPause = false; + + /// + /// 상태머신이 실제 동작중인지 확인합니다. + /// 검사상태 : Step = Run, IsThreadRun, bPause = false + /// + public Boolean IsRun + { + get + { + if (Step == eSMStep.RUN && _isthreadrun && bPause == false) return true; + else return false; + } + } + } } diff --git a/Cs_HMI/SubProject/AGV/Command.cs b/Cs_HMI/SubProject/AGV/Command.cs index 51cef83..8f06845 100644 --- a/Cs_HMI/SubProject/AGV/Command.cs +++ b/Cs_HMI/SubProject/AGV/Command.cs @@ -12,7 +12,7 @@ using AR; namespace arDev { - public partial class Narumi : arRS232 + public partial class Narumi { public bool AGVMoveSet(BunkiData opt) @@ -236,12 +236,12 @@ namespace arDev case eAgvCmd.ChargeOf: cmdString = $"CBT{param}O0003"; ///0003=충전대기시간 retval = AddCommand(cmdString); - RaiseMessage(arRS232.MessageType.Normal, "충전취소전송"); + RaiseMessage(NarumiSerialComm.MessageType.Normal, "충전취소전송"); break; case eAgvCmd.ChargeOn: cmdString = $"CBT{param}I0003"; ///0003=충전대기시간 retval = AddCommand(cmdString); - RaiseMessage(arRS232.MessageType.Normal, "충전명령전송"); + RaiseMessage(NarumiSerialComm.MessageType.Normal, "충전명령전송"); break; case eAgvCmd.TurnLeft: diff --git a/Cs_HMI/SubProject/AGV/DataEventArgs.cs b/Cs_HMI/SubProject/AGV/DataEventArgs.cs index 2ac94f0..0001ca3 100644 --- a/Cs_HMI/SubProject/AGV/DataEventArgs.cs +++ b/Cs_HMI/SubProject/AGV/DataEventArgs.cs @@ -2,7 +2,7 @@ namespace arDev { - public partial class Narumi + public partial class Narumi { public class DataEventArgs : EventArgs { diff --git a/Cs_HMI/SubProject/AGV/Dataframe.cs b/Cs_HMI/SubProject/AGV/Dataframe.cs new file mode 100644 index 0000000..4250003 --- /dev/null +++ b/Cs_HMI/SubProject/AGV/Dataframe.cs @@ -0,0 +1,69 @@ +using System; +using System.Linq; +using AR; + +namespace arDev +{ + public partial class Narumi + { + public class Dataframe + { + public byte STX { get; private set; } + public byte ETX { get; private set; } + public byte[] Data { get; private set; } + public string DataString { get; private set; } + public byte[] checksum { get; private set; } = new byte[2]; + public bool Valid { get; private set; } = false; + public string Message { get; private set; } = string.Empty; + public byte[] Buffer { get; private set; } + public string Cmd { get; private set; } = string.Empty; + + public bool Parse(byte[] data, int MinRecvLength = 0) + { + if (data == null || data.Any() == false) + { + this.Message = string.Format("수신 데이터가 없습니다"); + return false; + } + else if (data.Length < 5) + { + this.Message = $"데이터의 길이가 5보다 작습니다 길이={data.Length}"; + return false; + } + else if (MinRecvLength > 0 && data.Length < MinRecvLength) + { + this.Message = $"데이터의 길이가 {MinRecvLength}보다 작습니다 길이={data.Length}"; + return false; + } + else if (data[0] != 0x02 || data[data.Length - 1] != 0x03) + { + this.Message = $"STX/ETX Error"; + return false; + } + Buffer = new byte[data.Length]; + Array.Copy(data, Buffer, data.Length); + STX = data[0]; + ETX = data[data.Length - 1]; + Array.Copy(data, data.Length - 3, checksum, 0, 2); + + Data = new byte[data.Length - 4]; + Array.Copy(data, 1, Data, 0, data.Length - 4); + + if (data.Length > 2) Cmd = System.Text.Encoding.Default.GetString(Data, 0, 3); + + this.DataString = System.Text.Encoding.Default.GetString(Data); + + + Valid = true; + return true; + } + public Dataframe(byte[] buffer = null, int minlen = 0) + { + if (buffer != null) Parse(buffer); + } + } + + } + + +} diff --git a/Cs_HMI/SubProject/AGV/NARUMI.csproj b/Cs_HMI/SubProject/AGV/NARUMI.csproj index b1b1492..0a6434c 100644 --- a/Cs_HMI/SubProject/AGV/NARUMI.csproj +++ b/Cs_HMI/SubProject/AGV/NARUMI.csproj @@ -47,8 +47,10 @@ + + diff --git a/Cs_HMI/SubProject/AGV/Narumi.cs b/Cs_HMI/SubProject/AGV/Narumi.cs index 86ee95c..e4cc31f 100644 --- a/Cs_HMI/SubProject/AGV/Narumi.cs +++ b/Cs_HMI/SubProject/AGV/Narumi.cs @@ -11,15 +11,13 @@ using AR; namespace arDev { - public partial class Narumi : arRS232 + public partial class Narumi : arDev.NarumiSerialComm { Hashtable SystemCheck, ErrorCheck; private Queue Errlog; // 에러발생시 코드 임시 저장용(쓰레드 동기화용) - public int nBatteryNo { get; set; } = 0; - public Narumi() { SystemCheck = new Hashtable(); @@ -124,75 +122,18 @@ namespace arDev return bComplete; } - public class Dataframe - { - public byte STX { get; private set; } - public byte ETX { get; private set; } - public byte[] Data { get; private set; } - public string DataString { get; private set; } - public byte[] checksum { get; private set; } = new byte[2]; - public bool Valid { get; private set; } = false; - public string Message { get; private set; } = string.Empty; - public byte[] Buffer { get; private set; } - public string Cmd { get; private set; } = string.Empty; - - public bool Parse(byte[] data, int MinRecvLength = 0) - { - if (data == null || data.Any() == false) - { - this.Message = string.Format("수신 데이터가 없습니다"); - return false; - } - else if (data.Length < 5) - { - this.Message = $"데이터의 길이가 5보다 작습니다 길이={data.Length}"; - return false; - } - else if (MinRecvLength > 0 && data.Length < MinRecvLength) - { - this.Message = $"데이터의 길이가 {MinRecvLength}보다 작습니다 길이={data.Length}"; - return false; - } - else if (data[0] != 0x02 || data[data.Length - 1] != 0x03) - { - this.Message = $"STX/ETX Error"; - return false; - } - Buffer = new byte[data.Length]; - Array.Copy(data, Buffer, data.Length); - STX = data[0]; - ETX = data[data.Length - 1]; - Array.Copy(data, data.Length - 3, checksum, 0, 2); - - Data = new byte[data.Length - 4]; - Array.Copy(data, 1, Data, 0, data.Length - 4); - - if (data.Length > 2) Cmd = System.Text.Encoding.Default.GetString(Data, 0, 3); - - this.DataString = System.Text.Encoding.Default.GetString(Data); - - - Valid = true; - return true; - } - public Dataframe(byte[] buffer = null, int minlen = 0) - { - if (buffer != null) Parse(buffer); - } - } - public override bool ProcessRecvData(byte[] data) { //LastReceiveBuffer var frame = new Dataframe(data, MinRecvLength); if (frame.Valid == false) { - RaiseMessage(arRS232.MessageType.Error, frame.Message); + RaiseMessage(MessageType.Error, frame.Message); return false; } else if (frame.DataString.StartsWith("$") == false && CheckSum(data) == false) { - RaiseMessage(arRS232.MessageType.Error, "Checksum Error MSG=" + frame.DataString); + RaiseMessage(MessageType.Error, "Checksum Error MSG=" + frame.DataString); return false; } @@ -218,7 +159,7 @@ namespace arDev // $로 시작되는 AGV 상태 표시 //var text_Sts_Etc = Encoding.Default.GetString(bRcvData, 3, bRcvData.Length - 2).TrimStart(' '); //20210311 김정만 - SmartX FrameWork 사용 안함으로 주석처리 //var sMessageOther = Encoding.Default.GetString(bRcvData, 3, bRcvData.Length - 2).TrimStart(' '); - RaiseMessage(arRS232.MessageType.Normal, "$메세지수신:" + frame.DataString); + RaiseMessage(MessageType.Normal, "$메세지수신:" + frame.DataString); } else { @@ -228,7 +169,7 @@ namespace arDev } catch (Exception ex) { - RaiseMessage(arRS232.MessageType.Error, ex.Message); + RaiseMessage(MessageType.Error, ex.Message); retval = false; } } diff --git a/Cs_HMI/SubProject/AGV/NarumiSerialComm.cs b/Cs_HMI/SubProject/AGV/NarumiSerialComm.cs new file mode 100644 index 0000000..7725af9 --- /dev/null +++ b/Cs_HMI/SubProject/AGV/NarumiSerialComm.cs @@ -0,0 +1,494 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading; + +namespace arDev +{ + public abstract class NarumiSerialComm : ISerialComm, IDisposable + { + protected System.IO.Ports.SerialPort _device; + protected ManualResetEvent _mre; + protected byte[] LastReceiveBuffer = new byte[] { }; + /// + /// 최종 전송 메세지 + /// + 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; } + public DateTime LastConnTime { get; set; } + public DateTime LastConnTryTime { get; set; } + public DateTime lastSendTime; + /// + /// 메세지 수신시 사용하는 내부버퍼 + /// + protected List _buffer = new List(); + /// + /// 데이터조회간격(초) + /// + public float ScanInterval { get; set; } + + // public byte[] LastRecvData; + public string LastRecvString + { + get + { + if (LastReceiveBuffer == null) return String.Empty; + else return System.Text.Encoding.Default.GetString(LastReceiveBuffer); + } + } + /// + /// 마지막으로 데이터를 받은 시간 + /// + public DateTime lastRecvTime; + + + public int WriteError = 0; + public string WriteErrorMessage = string.Empty; + public int WaitTimeout { get; set; } = 1000; + public int MinRecvLength { get; set; } = 1; + /// + /// 포트이름 + /// + [Description("시리얼 포트 이름")] + [Category("설정"), DisplayName("Port Name")] + public string PortName + { + get + { + if (_device == null) return string.Empty; + else return _device.PortName; + } + set + { + if (this.IsOpen) + { + Message?.Invoke(this, new MessageEventArgs("포트가 열려있어 포트이름을 변경할 수 없습니다", true)); + } + else if (String.IsNullOrEmpty(value) == false) + _device.PortName = value; + else + { + Message?.Invoke(this, new MessageEventArgs("No PortName", true)); + } + } + } + + public int BaudRate + { + get + { + if (_device == null) return 0; + else return _device.BaudRate; + } + set + { + if (this.IsOpen) + { + Message?.Invoke(this, new MessageEventArgs("포트가 열려있어 BaudRate(를) 변경할 수 없습니다", true)); + } + else if (value != 0) + _device.BaudRate = value; + else Message?.Invoke(this, new MessageEventArgs("No baud rate", true)); + } + } + + + public NarumiSerialComm() + { + _device = new System.IO.Ports.SerialPort(); + this.BaudRate = 9600; + ScanInterval = 10; + _device.DataReceived += barcode_DataReceived; + _device.ErrorReceived += this.barcode_ErrorReceived; + _device.WriteTimeout = 3000; + _device.ReadTimeout = 3000; + _device.ReadBufferSize = 8192; + _device.WriteBufferSize = 8192; + //_device.DiscardInBuffer(); + //_device.DiscardOutBuffer(); + ErrorMessage = string.Empty; + lastRecvTime = DateTime.Parse("1982-11-23"); + LastConnTime = DateTime.Parse("1982-11-23"); + LastConnTryTime = DateTime.Parse("1982-11-23"); + lastRecvTime = DateTime.Parse("1982-11-23"); + this._mre = new ManualResetEvent(true); + } + + ~NarumiSerialComm() + { + Dispose(false); + } + + + // Flag: Has Dispose already been called? + bool disposed = false; + + // Public implementation of Dispose pattern callable by consumers. + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + // Protected implementation of Dispose pattern. + protected virtual void Dispose(bool disposing) + { + if (disposed) + return; + + if (disposing) + { + // Free any other managed objects here. + // + } + + _device.DataReceived -= barcode_DataReceived; + _device.ErrorReceived -= this.barcode_ErrorReceived; + + // Free any unmanaged objects here. + // + disposed = true; + } + + public Boolean Open() + { + try + { + _device.Open(); + return IsOpen; + } + catch (Exception ex) + { + ErrorMessage = ex.Message; + Message.Invoke(this, new MessageEventArgs(ex.Message, true)); + return false; + } + } + public string GetHexString(Byte[] input) + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + foreach (byte b in input) + sb.Append(" " + b.ToString("X2")); + return sb.ToString(); + } + + /// + /// 포트가 열려있는지 확인 + /// + [Description("현재 시리얼포트가 열려있는지 확인합니다")] + [Category("정보"), DisplayName("Port Open")] + public Boolean IsOpen + { + get + { + if (_device == null) return false; + return _device.IsOpen; + } + } + + public virtual bool Close() + { + if (_device != null && _device.IsOpen) + { + _device.DiscardInBuffer(); + _device.DiscardOutBuffer(); + _device.Close(); //dispose에서는 포트를 직접 클리어하지 않게 해뒀다. + return true; + } + else return false; + } + protected Boolean RaiseRecvData() + { + return RaiseRecvData(LastReceiveBuffer.ToArray(), false); + } + /// + /// 수신받은 메세지를 발생 시킵니다 + /// + /// + /// + public virtual Boolean RaiseRecvData(byte[] Data, bool udpatelastbuffer) + { + //181206 - 최종수신 메세지 기록 + lastRecvTime = DateTime.Now; + if (udpatelastbuffer && Data != null) + { + if (LastReceiveBuffer == null || LastReceiveBuffer.Length != Data.Length) + { + LastReceiveBuffer = new byte[Data.Length]; + Array.Copy(Data, LastReceiveBuffer, Data.Length); + } + } + + try + { + Message?.Invoke(this, new MessageEventArgs(Data, true)); //recvmessage + if (ProcessRecvData(Data) == false) + { + //Message?.Invoke(this, new MessageEventArgs(Data, true)); //recvmessage + Message?.Invoke(this, new MessageEventArgs(this.ErrorMessage, true)); //errormessage + return false; + } + else + { + + return true; + } + } + catch (Exception ex) + { + this.ErrorMessage = ex.Message; + this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true)); + return false; + } + } + + /// + /// 수신받은 자료를 처리한다 + /// + /// + /// + public abstract bool ProcessRecvData(byte[] data); + + #region "Internal Events" + + void barcode_ErrorReceived(object sender, System.IO.Ports.SerialErrorReceivedEventArgs e) + { + Message?.Invoke(this, new MessageEventArgs(e.ToString(), true)); + } + + byte[] buffer = new byte[] { }; + void barcode_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e) + { + + try + { + int ReadCount = _device.BytesToRead; + + buffer = new byte[ReadCount]; + _device.Read(buffer, 0, buffer.Length); + + System.Text.StringBuilder LogMsg = new StringBuilder(); + + byte[] remainBuffer; + Repeat: + if (CustomParser(buffer, out remainBuffer)) + { + //분석완료이므로 받은 데이터를 버퍼에 기록한다 + if (LastReceiveBuffer == null || (LastReceiveBuffer.Length != tempBuffer.Count)) + Array.Resize(ref LastReceiveBuffer, tempBuffer.Count); + Array.Copy(tempBuffer.ToArray(), LastReceiveBuffer, tempBuffer.Count); + tempBuffer.Clear(); + + //수신메세지발생 + RaiseRecvData(); + if (remainBuffer != null && remainBuffer.Length > 0) + { + //버퍼를 변경해서 다시 전송을 해준다. + Array.Resize(ref buffer, remainBuffer.Length); + Array.Copy(remainBuffer, buffer, remainBuffer.Length); + goto Repeat; //남은 버퍼가 있다면 진행을 해준다. + } + } + + } + catch (Exception ex) + { + //if (IsOpen) + //{ + // //_device.DiscardInBuffer(); + // //_device.DiscardOutBuffer(); + //} + ErrorMessage = ex.Message; + this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true)); + } + + } + + + #endregion + + #region "External Events" + + + /// + /// 오류 및 기타 일반 메세지 + /// + public event EventHandler Message; + + #endregion + + #region "Event Args" + + /// + /// 데이터를 수신할떄 사용함(RAW 포함) + /// + public class ReceiveDataEventArgs : EventArgs + { + private byte[] _buffer = null; + + /// + /// 바이트배열의 버퍼값 + /// + public byte[] Value { get { return _buffer; } } + + /// + /// 버퍼(바이트배열)의 데이터를 문자로 반환합니다. + /// + public string StrValue + { + get + { + //return string.Empty; + + if (_buffer == null || _buffer.Length < 1) return string.Empty; + else return System.Text.Encoding.Default.GetString(_buffer); + } + } + public ReceiveDataEventArgs(byte[] buffer) + { + _buffer = buffer; + } + } + + /// + /// 메세지를 강제 발생 + /// + /// + /// + protected virtual void RaiseMessage(MessageType mt, string message) + { + this.Message?.Invoke(this, new MessageEventArgs(mt, message)); + } + public enum MessageType + { + Normal, + Error, + Send, + Recv, + } + + public class MessageEventArgs : EventArgs + { + public MessageType MsgType { get; set; } + private string _message = string.Empty; + + /// + /// Recv,Send,Normal,Error 모두 지원 + /// + public string Message { get { return _message; } } + + private byte[] _data = null; + + /// + /// Recv,Send에서만 값이 존재 합니다 + /// + public byte[] Data { get { return _data; } } + public MessageEventArgs(string Message, bool isError = false) + { + if (isError) MsgType = MessageType.Error; + else MsgType = MessageType.Normal; + _message = Message; + } + public MessageEventArgs(MessageType msgtype, string Message) + { + MsgType = msgtype; + _message = Message; + _data = System.Text.Encoding.Default.GetBytes(Message); + } + + public MessageEventArgs(byte[] buffer, bool isRecv = true) + { + if (isRecv) MsgType = MessageType.Recv; + else MsgType = MessageType.Send; + _data = new byte[buffer.Length]; + Array.Copy(buffer, _data, Data.Length); + _message = System.Text.Encoding.Default.GetString(_data); + } + + } + + #endregion + + + + protected abstract bool CustomParser(byte[] buf, out byte[] remainBuffer); + + /// + /// 포트가 열려있거나 데이터 수신시간이 없는경우 false를 반환합니다 + /// + public Boolean IsValid + { + get + { + if (IsOpen == false) return false; + if (lastRecvTime.Year == 1982) return false; + var ts = DateTime.Now - lastRecvTime; + if (ts.TotalSeconds > (this.ScanInterval * 2.5)) return false; + return true; + } + } + protected bool WriteData(string cmd) + { + return WriteData(System.Text.Encoding.Default.GetBytes(cmd)); + } + /// + /// 포트에 쓰기(barcode_DataReceived 이벤트로 메세지수신) + /// + protected Boolean WriteData(byte[] data) + { + Boolean bRet = false; + + //171205 : 타임아웃시간추가 + if (!_mre.WaitOne(WaitTimeout)) + { + ErrorMessage = $"WriteData:MRE:WaitOne:TimeOut {WaitTimeout}ms"; + this.Message?.Invoke(this, new MessageEventArgs(ErrorMessage, true)); + return false; + } + + _mre.Reset(); + + //Array.Resize(ref data, data.Length + 2); + + try + { + lastSendTime = DateTime.Now; + if (lastSendBuffer == null) lastSendBuffer = new byte[data.Length]; //171113 + else Array.Resize(ref lastSendBuffer, data.Length); + Array.Copy(data, lastSendBuffer, data.Length); + + for (int i = 0; i < data.Length; i++) + _device.Write(data, i, 1); + + //_device.Write(data, 0, data.Length); + + //171113 + this.Message?.Invoke(this, new MessageEventArgs(data, false)); + + bRet = true; + WriteError = 0; + WriteErrorMessage = string.Empty; + } + catch (Exception ex) + { + // this.isinit = false; + this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true)); + bRet = false; + WriteError += 1; //연속쓰기오류횟수 + WriteErrorMessage = ex.Message; + } + finally + { + _mre.Set(); + } + return bRet; + } + } + + +} diff --git a/Cs_HMI/SubProject/CommData/CommData.csproj b/Cs_HMI/SubProject/CommData/CommData.csproj index c2e1795..b5ceab2 100644 --- a/Cs_HMI/SubProject/CommData/CommData.csproj +++ b/Cs_HMI/SubProject/CommData/CommData.csproj @@ -44,6 +44,7 @@ + diff --git a/Cs_HMI/SubProject/CommData/ISerialComm.cs b/Cs_HMI/SubProject/CommData/ISerialComm.cs new file mode 100644 index 0000000..e454c2a --- /dev/null +++ b/Cs_HMI/SubProject/CommData/ISerialComm.cs @@ -0,0 +1,18 @@ +using System; + +namespace arDev +{ + /// + /// 시리얼통신모듈의기본형태정의 + /// + public interface ISerialComm + { + string PortName { get; set; } + int BaudRate { get; set; } + bool IsOpen { get; } + string ErrorMessage { get; set; } + + Boolean Open(); + Boolean Close(); + } +} diff --git a/Cs_HMI/SubProject/CommData/RS232.cs b/Cs_HMI/SubProject/CommData/RS232.cs index 27c28ae..fa00a38 100644 --- a/Cs_HMI/SubProject/CommData/RS232.cs +++ b/Cs_HMI/SubProject/CommData/RS232.cs @@ -7,7 +7,8 @@ using System.Threading; namespace arDev { - public abstract partial class arRS232 : IDisposable + + public abstract partial class arRS232 : ISerialComm, IDisposable { protected System.IO.Ports.SerialPort _device; protected ManualResetEvent _mre; @@ -19,7 +20,7 @@ namespace arDev //public int ValidCheckTimeMSec { get; set; } = 5000; protected List tempBuffer = new List(); protected Boolean findSTX = false; - public string errorMessage { get; set; } + public string ErrorMessage { get; set; } public DateTime LastConnTime { get; set; } public DateTime LastConnTryTime { get; set; } public DateTime lastSendTime; @@ -111,7 +112,7 @@ namespace arDev _device.ReadBufferSize = 8192; _device.WriteBufferSize = 8192; - errorMessage = string.Empty; + ErrorMessage = string.Empty; lastRecvTime = DateTime.Parse("1982-11-23"); LastConnTime = DateTime.Parse("1982-11-23"); LastConnTryTime = DateTime.Parse("1982-11-23"); @@ -164,7 +165,7 @@ namespace arDev } catch (Exception ex) { - errorMessage = ex.Message; + ErrorMessage = ex.Message; Message.Invoke(this, new MessageEventArgs(ex.Message, true)); return false; } @@ -191,14 +192,16 @@ namespace arDev } } - public virtual void Close(Boolean PortClose = true) + public virtual bool Close() { if (_device != null && _device.IsOpen) { _device.DiscardInBuffer(); _device.DiscardOutBuffer(); - if (PortClose) _device.Close(); //dispose에서는 포트를 직접 클리어하지 않게 해뒀다. + _device.Close(); //dispose에서는 포트를 직접 클리어하지 않게 해뒀다. + return true; } + else return false; } protected Boolean RaiseRecvData() { @@ -228,7 +231,7 @@ namespace arDev if (ProcessRecvData(Data) == false) { //Message?.Invoke(this, new MessageEventArgs(Data, true)); //recvmessage - Message?.Invoke(this, new MessageEventArgs(this.errorMessage, true)); //errormessage + Message?.Invoke(this, new MessageEventArgs(this.ErrorMessage, true)); //errormessage return false; } else @@ -239,7 +242,7 @@ namespace arDev } catch (Exception ex) { - this.errorMessage = ex.Message; + this.ErrorMessage = ex.Message; this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true)); return false; } @@ -301,7 +304,7 @@ namespace arDev // //_device.DiscardInBuffer(); // //_device.DiscardOutBuffer(); //} - errorMessage = ex.Message; + ErrorMessage = ex.Message; this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true)); } @@ -419,7 +422,7 @@ namespace arDev /// /// 포트가 열려있거나 데이터 수신시간이 없는경우 false를 반환합니다 /// - public Boolean IsValid + public Boolean IsValid { get { @@ -444,8 +447,8 @@ namespace arDev //171205 : 타임아웃시간추가 if (!_mre.WaitOne(WaitTimeout)) { - errorMessage = $"WriteData:MRE:WaitOne:TimeOut {WaitTimeout}ms"; - this.Message?.Invoke(this, new MessageEventArgs(errorMessage, true)); + ErrorMessage = $"WriteData:MRE:WaitOne:TimeOut {WaitTimeout}ms"; + this.Message?.Invoke(this, new MessageEventArgs(ErrorMessage, true)); return false; } diff --git a/Cs_HMI/SubProject/EnigProtocol b/Cs_HMI/SubProject/EnigProtocol index 2a5bb77..2839104 160000 --- a/Cs_HMI/SubProject/EnigProtocol +++ b/Cs_HMI/SubProject/EnigProtocol @@ -1 +1 @@ -Subproject commit 2a5bb77dabf4fa2ead4cefe0ab382bec19cff500 +Subproject commit 283910459e0024d10e2f8eb55243c82dfc7c8e05 diff --git a/Cs_HMI/TestProject/Test_ACS/MainForm.cs b/Cs_HMI/TestProject/Test_ACS/MainForm.cs index 9785733..4bfc701 100644 --- a/Cs_HMI/TestProject/Test_ACS/MainForm.cs +++ b/Cs_HMI/TestProject/Test_ACS/MainForm.cs @@ -23,7 +23,7 @@ namespace Test_ACS LoadPortList(); } - + private void InitializeProtocol() { @@ -61,6 +61,14 @@ namespace Test_ACS case AGVCommandEH.Status: UpdateAGVStatus(e.ReceivedPacket.Data); break; + case AGVCommandEH.Error: + var errorcode = (AGVErrorCode)e.ReceivedPacket.Data[0]; + var errorMessage = System.Text.Encoding.UTF8.GetString(e.ReceivedPacket.Data, 1, e.ReceivedPacket.Data.Length - 1); + AddLog($"Error Received : {errorcode} ID:{e.ReceivedPacket.ID} MSG:{errorMessage}", LogType.Info); + break; + default: + AddLog($"unknown command:{command}", LogType.Error); + break; } } @@ -153,6 +161,7 @@ namespace Test_ACS serialPort.Close(); btnConnect.Text = "연결"; AddLog("포트 닫힘", LogType.Info); + } else { @@ -163,6 +172,7 @@ namespace Test_ACS serialPort.Open(); btnConnect.Text = "해제"; AddLog($"{serialPort.PortName}:{serialPort.BaudRate} 연결됨", LogType.Info); + SaveSettings(); } catch (Exception ex) { @@ -178,22 +188,22 @@ namespace Test_ACS private void cmbPort_SelectedIndexChanged(object sender, EventArgs e) { - SaveSettings(); + //SaveSettings(); } private void txtBaudRate_TextChanged(object sender, EventArgs e) { - SaveSettings(); + //SaveSettings(); } private void txtRFID_TextChanged(object sender, EventArgs e) { - SaveSettings(); + //SaveSettings(); } private void txtAlias_TextChanged(object sender, EventArgs e) { - SaveSettings(); + //SaveSettings(); } private void rbAGV1_CheckedChanged(object sender, EventArgs e) @@ -201,7 +211,7 @@ namespace Test_ACS if (rbAGV1.Checked) { selectedAGV = 11; - SaveSettings(); + //SaveSettings(); } } @@ -210,7 +220,7 @@ namespace Test_ACS if (rbAGV2.Checked) { selectedAGV = 12; - SaveSettings(); + //SaveSettings(); } } @@ -250,6 +260,7 @@ namespace Test_ACS var aliasHex = string.Join("", aliasBytes.Select(b => b.ToString("X2"))); var dataStr = targetID + aliasHex; SendCommand(AGVCommandHE.GotoAlias, dataStr); + SaveSettings(); } private void btnStop_Click(object sender, EventArgs e) @@ -524,9 +535,6 @@ namespace Test_ACS protected override void OnFormClosing(FormClosingEventArgs e) { - // 설정 저장 - SaveSettings(); - if (serialPort != null && serialPort.IsOpen) { serialPort.Close();