using Aurora.Api; using Aurora; using AuroraDeviceCommon.Utils; using AuroraPLC; using Hermes; using System; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Text; using System.Threading.Tasks; using AuroraMelsecNet; using AuroraMXComponent; using System.Reactive.Linq; namespace WeldingCommon.Devices { // 메모리 맵핑을 abstract로 강제화하는 것 다시 추가(5:06) 다시제거(08:30) public abstract partial class PLCController : PLCBase { // Log 사용 방법 static Logger Log = new Logger("PLCController"); //static Logger EventLog = new Logger(eMsg_TYPE.PLC.ToString(), "Event"); //MonitorPlcInterfaceMap 스레드는 타이머로 구성 public MicroTimer _monitorPlcInterfaceMap = new MicroTimer(); // rising edge 를 판별하기 위한 이전 상태 기록 변수 private bool _bAutoRunStartCmd; private bool _bAutoRunStopCmd; private bool _bRecipeChangeCmd; private long _PCLiveCheckUpdateTime; private bool _bLastAutoRunBit; private bool _bLastManualBit; private IEventPublisher mEventPublisher; //공통 뷰에서 사용하는 Address 정보 //public int AutoCompleteAddr; //public int ManualCompleteAddr; //public int AutoReqAddr; //public int ManualReqAddr; //public int RecipeReqAddr; //public int RecipeNoAddr; //public int LightAlarmStatusAddr; //public int HeavyAlarmStatusAddr; public int PLC_LIVE_STATUSAddr; public int PLC_AUTO_STATUSAddr; public int PLC_AUTO_REQAddr; public int PLC_MANUAL_REQAddr; public int PLC_EMO_STATUSAddr; public int PLC_DoorInterlockAddr; public int PLC_RECIPE_CHANGE_REQAddr; public int PLC_RECIPE_CHANGE_COMPLETE_ARKAddr; public int PLC_RECIPE_NOAddr; public int PLC_USE_DISPLACEMENT_SENSORAddr; public int PLC_STAGE_MOVINGAddr; public int PLC_WELD_REQEUSTAddr; public int PLC_JIG_LOAD_COMPLETEAddr; public int PLC_JIG_MOVE_BYPASSAddr; public int PLC_USE_WELD_JIG_1Addr; public int PLC_USE_WELD_JIG_2Addr; public int PLC_USE_WELD_JIG_3Addr; public int PLC_USE_WELD_JIG_4Addr; public int PLC_USE_WELD_JIG_5Addr; public int PLC_USE_WELD_JIG_6Addr; public int PLC_USE_WELD_JIG_7Addr; public int PLC_USE_WELD_JIG_8Addr; public int PLC_USE_WELD_JIG_9Addr; public int PLC_USE_WELD_JIG_10Addr; public int PLC_USE_WELD_JIG_11Addr; public int PLC_USE_WELD_JIG_12Addr; public int PLC_USE_WELD_JIG_13Addr; public int PLC_USE_WELD_JIG_14Addr; public int PLC_USE_WELD_JIG_15Addr; public int PLC_USE_WELD_JIG_16Addr; public int PC_LIVE_STATUSAddr; public int PC_AUTO_STATUSAddr; public int PC_AUTO_COMPLETEAddr; public int PC_MANUAL_COMPLETEAddr; public int PC_LIGHT_ALARM_STATUSAddr; public int PC_HEAVY_ALARM_STATUSAddr; public int PC_RECIPE_CHANGE_COMPLETEAddr; public int PC_WELD_REQEUST_ACKAddr; public int PC_JIG_LOAD_COMPLETE_ACKAddr; public int PC_WELD_COMPLETE_1Addr; public int PC_WELD_COMPLETE_2Addr; public int PC_WELD_COMPLETE_3ADdr; public int PC_WELD_COMPLETE_4Addr; public int PC_WELD_COMPLETE_5Addr; public int PC_WELD_COMPLETE_6Addr; public int PC_WELD_COMPLETE_7Addr; public int PC_WELD_COMPLETE_8Addr; public int PC_WELD_COMPLETE_9Addr; public int PC_WELD_COMPLETE_10Addr; public int PC_WELD_COMPLETE_11Addr; public int PC_WELD_COMPLETE_12Addr; public int PC_WELD_COMPLETE_13Addr; public int PC_WELD_COMPLETE_14Addr; public int PC_WELD_COMPLETE_15Addr; public int PC_WELD_COMPLETE_16Addr; public int PC_LASER_READYAddr; public int PC_LASER_ONAddr; #region 생성자 IAuroraEnvironment _AuroraEnv; public PLCController(string deviceId, ePLCNetType plcNetType, PLCMMapFileType fileType, IAuroraEnvironment aurora) : base(deviceId) { _AuroraEnv = aurora; mEventPublisher = _AuroraEnv.EventPublisher; // PLC Network 생성 // PLCNet 의 생명주기 관리 NetworkType = plcNetType; try { if (plcNetType == ePLCNetType.MelsecNet) { // MelsecNet #warning MXComponent 사용 시 AuroraMelsecNet Reference 삭제 및 new MelsecNet 코드 주석처리 PLCNet = new MelsecNet(); } else if (plcNetType == ePLCNetType.MXComponent) { // MxComponent #warning MelsecNet 사용 시 AuroraMXComponent Reference 삭제 및 new MXComponent 코드 주석처리 PLCNet = new MXComponent(); // 주석 해제 필요 } else { // 통신 프로토콜 추가시, 여기에 추가... // 에러 Log.E("Unsupported network type"); } // 메모리맵 파싱 관련 mmapFileType = fileType; //생성자에서 메모리 맵핑이 완료되야됨. LoadMemoryMapFromFiles(); // PLC Motions 의 시뮬레이션은 추후 재검토 필요 if (PLCNet is null) { Log.E("PLC Network (MXComponent or MelsecNet) is not existed"); } _PCLiveCheckUpdateTime = DateTimeOffset.Now.ToUnixTimeMilliseconds(); #region PLC 모션축 세팅 Observable.Merge( aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_DEVICE_ACTIVATED), aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_DEVICE_DEACTIVATED)) .Select(msg => msg.Param as string) .Where(x => x == deviceId) .Subscribe(x => OnDeviceActiveChanged() ); #endregion } catch (Exception ex) { Log.FuncE(ex.Message); PLCNet = null; } // ILSOS PLC 반영 사항 _bAutoRunStartCmd = false; _bAutoRunStopCmd = false; _bRecipeChangeCmd = false; // Event Publisher 확인(이전 PLC에 있는 타이머 부분 추가) _monitorPlcInterfaceMap.OnTimeout = MonitorPlcInterfaceMap; // 공통부 뷰에서 사용하는 Address 정보를 각 장비 프로젝트에서 가져옴 MappingPLCAddr(); } public abstract void MappingPLCAddr(); private void MonitorPlcInterfaceMap() { UpdatePCLiveStatus(); #region PLC 신호중에 Command 신호 들어온 것에 대한 처리. rising edge 인 경우에만 반응하도록 코딩할 것 // EMO는 B접으로 처리되어 항상 True이다. if (!ReadBit(PLC_EMO_STATUSAddr).data) { _bAutoRunStartCmd = false; _bAutoRunStopCmd = false; } //if (PlcDIOValues["PLC_AUTO_REQ"] && PlcDIOValues["PLC_MANUAL_REQ"]) if (ReadBit(PLC_AUTO_REQAddr).data && ReadBit(PLC_MANUAL_REQAddr).data) { // Auto Run 시작요청과 Manual 전환 요청이 동시에 들어온 경우에는 둘다 무시한다. // Falling Edge 후에 Rising Edge 되면 동작되도록 이전 상태 변수는 넣어둔다. _bAutoRunStartCmd = true; _bAutoRunStopCmd = true; Log.I("PLC 에서 Auto Run Start 와 Manual 전환 신호가 동시에 들어왔습니다. 이럴 경우 2개 신호를 껐다가 다시 하나가 켜질때만 동작됩니다."); } else { if(ReadBit(PLC_AUTO_REQAddr).data != _bLastAutoRunBit) { _bLastAutoRunBit = ReadBit(PLC_AUTO_REQAddr).data; Log.D($"PLC AutoRun Bit Change - {_bLastAutoRunBit}, _bAutoRunStartCmd : {_bAutoRunStartCmd}, _bAutoRunStopCmd : {_bAutoRunStopCmd}"); } if (ReadBit(PLC_MANUAL_REQAddr).data != _bLastManualBit) { _bLastManualBit = ReadBit(PLC_MANUAL_REQAddr).data; Log.D($"PLC Manual Bit Change - {_bLastManualBit}, _bAutoRunStartCmd : {_bAutoRunStartCmd}, _bAutoRunStopCmd : {_bAutoRunStopCmd}"); } if (_bAutoRunStartCmd) { // 이전에 Cmd 가 들어왔었다면 PLC 에서 cmd 가 꺼지면 꺼준다. (true → false 변하는 falling edge 만 반응할것) if (!ReadBit(PLC_AUTO_REQAddr).data) { _bAutoRunStartCmd = false; //SetDOStatus("PC_AUTO_COMPLETE", false); WriteBit(PC_AUTO_COMPLETEAddr, false); Log.I("PLC Auto Request OFF"); } } else { // Cmd 가 처음 들어왔다면 Event 발생시킨다. (false → true 변하는 rising edge 만 반응할것) //if (PlcDIOValues["PLC_AUTO_REQ"]) { if (ReadBit(PLC_AUTO_REQAddr).data) { _bAutoRunStartCmd = true; _bAutoRunStopCmd = false; // stop 과 start는 주소가 달라서 동시에 들어올수는 있어도 동시처리하지 않는다. //const string EVENT_AUTO_RUN_START_REQUEST = "EventAutoRunStartRequest";//"AutoRunStartRequest"; mEventPublisher.Publish(new EventMessage("PLC", CommonEventIds.EVENT_AUTO_RUN_START_REQUEST)); //SetDOStatus("PC_MANUAL_COMPLETE", false); WriteBit(PC_MANUAL_COMPLETEAddr, false); Log.I("PLC Auto Request ON"); } } if (_bAutoRunStopCmd) { // 이전에 Cmd 가 들어왔었다면 PLC 에서 cmd 가 꺼지면 꺼준다. (true → false 변하는 falling edge 만 반응할것) //if (!PlcDIOValues["PLC_MANUAL_REQ"]) { if (!ReadBit(PLC_MANUAL_REQAddr).data) { _bAutoRunStopCmd = false; //SetDOStatus("PC_MANUAL_COMPLETE", false); WriteBit(PC_MANUAL_COMPLETEAddr, false); Log.I("PLC Manual Request OFF"); } } else { // Cmd 가 처음 들어왔다면 Event 발생시킨다. (false → true 변하는 rising edge 만 반응할것) //if (PlcDIOValues["PLC_MANUAL_REQ"]) { if (ReadBit(PLC_MANUAL_REQAddr).data) { _bAutoRunStopCmd = true; _bAutoRunStartCmd = false; // stop 과 start는 주소가 달라서 동시에 들어올수는 있어도 동시처리하지 않는다. //const string EVENT_AUTO_RUN_STOP_REQUEST = "EventAutoRunStopRequest";//"AutoRunStopRequest"; mEventPublisher.Publish(new EventMessage("PLC", CommonEventIds.EVENT_AUTO_RUN_STOP_REQUEST)); Log.I("PLC Manual Request ON"); } } if (_bRecipeChangeCmd) { // 이전에 Cmd 가 들어왔었다면 PLC 에서 cmd 가 꺼지면 꺼준다. (true → false 변하는 falling edge 만 반응할것) //if (!PlcDIOValues["PLC_RECIPE_CHANGE_REQ"]) { if (!ReadBit(PLC_RECIPE_CHANGE_REQAddr).data) { _bRecipeChangeCmd = false; //SetDOStatus("PC_AUTO_COMPLETE", false); WriteBit(PC_AUTO_COMPLETEAddr, false); Log.I("PLC Recipe Change Request OFF"); } } else { // Cmd 가 처음 들어왔다면 Event 발생시킨다. (false → true 변하는 rising edge 만 반응할것) //if (PlcDIOValues["PLC_RECIPE_CHANGE_REQ"]) { if (ReadBit(PLC_RECIPE_CHANGE_REQAddr).data) { _bRecipeChangeCmd = true; // EventIds 를 호출할수없다. string 이 같은 값을 가지도록 주의하자. //const string EVENT_RECIPE_CHANGE_REQUEST = "EventRecipeChangeRequest";//"RecipeChangeRequest"; //int nChangeRecipeNum = Convert.ToInt32(PlcAIOValues["PLC_RECIPE_NO"]); // WORD 정보는 PlcAIOValues 에 넣어놓은듯. int nChangeRecipeNum = Convert.ToInt32(ReadWord(PLC_RECIPE_NOAddr).data); // WORD 정보는 PlcAIOValues 에 넣어놓은듯. mEventPublisher.Publish(new EventMessage("PLC", CommonEventIds.EVENT_RECIPE_CHANGE_REQUEST, nChangeRecipeNum)); Log.I("PLC Recipe Change Request ON"); } } } #endregion } public void UpdatePCLiveStatus() { long lPCLiveCheckTimeTerm = 100; // 100ms long updatetime = DateTimeOffset.Now.ToUnixTimeMilliseconds(); if (PLCNet == null || IsActivated == false) return; if (Math.Abs(updatetime - _PCLiveCheckUpdateTime) > lPCLiveCheckTimeTerm) { bool status = ReadBit(PC_LIVE_STATUSAddr, ePLCDevice.B, false).data; WriteBit(PC_LIVE_STATUSAddr, !status); _PCLiveCheckUpdateTime = updatetime; } } public void RegisterMotion() { #region PLC 모션축 세팅 if (HasMotionAxes && _plcMotorList.Count == 0) { // 모션축 등록.... (수정필요) //_plcMotorList = new List(); //var motorList = DeviceIds.GetMotorDeviceIDs(); //foreach (var item in motorList) { // _plcMotorList.Add(_AuroraEnv.DeviceManager[item] as PLCMotorBase); //} //// 별칭 설정 (인덱스는 ACS 랑 차후 검토 필요함...) (Inkjet 은 ACS 축은 없음) //stageX = _plcMotorList[(int)eAxis.Stage_X - (int)eAxis.Stage_X]; //headY = _plcMotorList[(int)eAxis.Head_Y - (int)eAxis.Stage_X]; //headZ = _plcMotorList[(int)eAxis.Head_Z - (int)eAxis.Stage_X]; //stageT = _plcMotorList[(int)eAxis.Stage_T - (int)eAxis.Stage_X]; //// 메모리맵 Config 를 Readonly 로 세팅함..각 모터 override 활성화에서 처리 } #endregion } #region 활성화/비활성화 protected override int DoActivate(IDeviceConfig config) { //============================================== // 로그 조건 세팅 //IsLogModePLCDetail = SolderingUtil.GetLogSetting().LOG_MODE_PLC_DETAIL; IsLogModePLCDetail = false; var res = base.DoActivate(config); //Activate할 때, 한번 더 주소 맵핑 필요 (맵핑이 생성자에서 통신 세팅보다 느림) LoadMemoryMapFromFiles(); if (res != E1.OK) { // PLC 통신 실패 return res; } //================================================ //_curConfigUpdateRateMS = _connConfig.UpdateRateMS; // update 타이머 세팅 _monitorPlcInterfaceMap.UpdateRateUS = CurConfigUpdateRateMS * 1000; //_dataUpdateTimer.FeedbackPerMS = 0; // No use //========================================= // Start the timer if(IsSimul == false) { _monitorPlcInterfaceMap.Start(); } else { } return res; } protected override int DoDeactivate() { var res = base.DoDeactivate(); if (res != E1.OK) { Log.E("Deactivate is failed"); return res; } return res; } private void OnDeviceActiveChanged() { if (IsActivated) { // PLC 연결 성공 // PLCMotion 스레드 관련 // 시뮬 모드이고, PLCMotorList 가 비어있지 않으면 모션 스레드 생성 if (IsSimul && _plcMotorList.Count > 0) { StartPLCMotionThread(); // 스레드 생성 및 시작 } // Mover 활성화(수정필요) //MachineMover.GetInstanceMachineMover()?.Activate(); } else { // Mover 비활성화 //MachineMover.GetInstanceMachineMover()?.Deactivate(); StopPLCMotionThread(); } } #endregion #region 재연결 public override int ReconnectNetwork() { var res = base.ReconnectNetwork(); return res; } #endregion #region 메모리맵 파일 파싱 public void LoadMemoryMapFromFiles() { /* if (!ParsePLCAlarmSettingAddress(AlarmSettingPath, ref _stAlarmList, ref _stSettingList, out ePLCErrCode retCode)) { if (retCode == ePLCErrCode.MemMapPaser_SETTINGALARM_LOAD_FAIL) { _alarmService.SetAlarm(AlarmCode.ALARM_PLC_EXCEL_SETTINGALARM_LOAD_FAIL); } Log.E("SettingAlarm MMap parsing is failed"); } */ // PLC 메모리 주소를 매핑함 InitPLCAddress(); // 정상여부 체크... if (BitInputs.Count == 0) { Log.W("No. of Bit Input MMap is zero"); } if (BitOutputs.Count == 0) { Log.W("No. of Bit Output MMap is zero"); } if (WordInputs.Count == 0) { Log.W("No. of Word Input MMap is zero"); } if (WordOutputs.Count == 0) { Log.W("No. of Word Output MMap is zero"); } } #endregion // 추후에 폴더 생성하고 옮기기 public enum eAxis { None = -1, //==================================== // Motor Example Stage_X = 0, Head_Y, Head_Z, Stage_T, AXIS_MAX_NUM }; } } #endregion