using Aurora.Api; using Hermes; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace WeldingCommon.Devices { public class PLCMonitor { private static readonly Logger Log = new Logger("PLCMonitor"); //private static readonly Logger EventLog = new Logger(eMsg_TYPE.PLC.ToString(), "Event"); private const int SLEEP_TIME_MS = 50; private const int SLEEP_TIME_FOR_CLOSE_MS = 200; private const int SLEEP_TIME_FOR_RECONNECT_MS = 10000; private const string SEQ_INIT = "Init"; private const string CHECK_ACTIVE = "Check PLC Activation"; private const string MONITOR_NETWORK = "Monitor PLC Network status"; private const string MONITOR_ALARM = "Monitor Alarm change"; private const string MONITOR_SETTING = "Monitor Setting change"; private const string RECONNECT_NETWORK = "Reconnect to PLC"; private const string GO_CHECK_ACTIVE = "Go to check active"; // 모아주는 노드 private const string GO_MONITOR_NETWORK = "Go to monitor network"; private const string PLC_IS_DEACTIVATING = "PLC is deactivating"; private const string GO_RECONNECT_NETWORK = "Reconnect PLC Phase"; private static PLCController _plc; const int TIME_OUT = 2000; // 2 [s] /* public enum eMainSeqStep { INIT, WORK, WAIT_MANUAL_SELECT, WAIT_MANUAL_SHOT_SELECT, VISION_WAIT, ACK_WAIT, FAIL, MAVIN_SEND_WAIT, MAVIN_SEND, MAVIN_RESULT_WAIT, } static eMainSeqStep Step_ALARM = new eMainSeqStep(); static eMainSeqStep Step_SETTING = new eMainSeqStep(); static Stopwatch swAlarm = new Stopwatch(); static Stopwatch swSetting = new Stopwatch(); */ //static ulong _latchForWriteBitUpdate; public static SequenceBuilder Builder() { SequenceBuilder sb = new SequenceBuilder(1000, true); // 무한, 시퀀스 Stop시 최대 대기시간. sb.AddAction(SEQ_INIT, Init); sb.AddDecision(CHECK_ACTIVE, CheckActive); sb.AddDecision(MONITOR_NETWORK, MonitorNetworkStatus); //-------------------- Setting/Alarm 로깅 부분 sb.AddDecision(MONITOR_ALARM, MonitorPLCAlarmStatus); sb.AddDecision(MONITOR_SETTING, MonitorPLCSettingStatus); //================================================== sb.AddDecision(RECONNECT_NETWORK, ReconnectPLCNetwork); sb.AddDecision(GO_CHECK_ACTIVE, GoToCheckActive); // 경유용 노드 (플로우 차트를 이쁘게 만들기 위한 목적) sb.AddAction(GO_MONITOR_NETWORK, GoToMonitorNetwork); sb.AddAction(PLC_IS_DEACTIVATING, PlcIsDeactivating); sb.AddAction(GO_RECONNECT_NETWORK, GoToReconnectNetwork); //=================================================== //---------------------------------------------------- //------ 스퀀스 플로우 차트 sb.Flows(sb.Init > sb[SEQ_INIT] > sb[CHECK_ACTIVE]); sb.Flows(sb[CHECK_ACTIVE] > sb[MONITOR_NETWORK]); sb.Flows(sb[CHECK_ACTIVE] > sb.Final); // 종료 //------------------- 주요 로직 // 루프 sb.Flows(sb[MONITOR_NETWORK] > sb[MONITOR_ALARM] > sb[MONITOR_SETTING] > sb[GO_MONITOR_NETWORK] > sb[MONITOR_NETWORK]); //------ 통신 재연결 루트 sb.Flows(sb[MONITOR_NETWORK] > sb[GO_RECONNECT_NETWORK]); sb.Flows(sb[MONITOR_ALARM] > sb[GO_RECONNECT_NETWORK]); sb.Flows(sb[MONITOR_SETTING] > sb[GO_RECONNECT_NETWORK]); // 루프 sb.Flows(sb[GO_RECONNECT_NETWORK] > sb[RECONNECT_NETWORK] > sb[GO_RECONNECT_NETWORK]); sb.Flows(sb[RECONNECT_NETWORK] > sb[GO_CHECK_ACTIVE]); //------ PLC 비활성화 루트 sb.Flows(sb[MONITOR_NETWORK] > sb[PLC_IS_DEACTIVATING]); sb.Flows(sb[MONITOR_ALARM] > sb[PLC_IS_DEACTIVATING]); sb.Flows(sb[MONITOR_SETTING] > sb[PLC_IS_DEACTIVATING]); sb.Flows(sb[PLC_IS_DEACTIVATING] > sb[GO_CHECK_ACTIVE] > sb[CHECK_ACTIVE]); //=================================================== sb.OnStopped(OnStopped); sb.OnForceError(OnForceError); //sb.Draw("PLCMonitoring.pdf"); return sb; } private static void OnForceError(SequenceContext arg1, string arg2) { Log.E("Enter Forced Error phase"); } private static void OnStopped(SequenceContext obj) { Log.I("Enter Stopped phase"); } private static string GoToCheckActive(SequenceContext context, string id, ICollection branches, SequenceActionResult param) { //Log.I("GO_CHECK_ACTIVE: --> CHECK_ACTIVE"); #if DEBUG //Debug.WriteLine($"GO_CHECK_ACTIVE: --> CHECK_ACTIVE"); #endif return CHECK_ACTIVE; } private static string ReconnectPLCNetwork(SequenceContext context, string id, ICollection branches, SequenceActionResult param) { if (_plc.IsActivated) { //EventLog.W($"PLC Reconnection Phase due to connection error"); // 여기는 독립 스레드이고, PLC 통신 연결이 실패하면, 다른 작업이 무의미하므로, 딜레이로 Sleep 을 사용함 if (_plc.ReconnectNetwork() != E1.OK) { Log.E("PLC reconnection is failed. Retry after 10 [s]"); Thread.Sleep(SLEEP_TIME_FOR_RECONNECT_MS); // 10 초 return GO_RECONNECT_NETWORK; } //_plc.WriteOffsetData(Data.UnitType.Max, null); //EventLog.I($"PLC is reconnected"); return GO_CHECK_ACTIVE; } return GO_CHECK_ACTIVE; } private static SequenceActionResult GoToReconnectNetwork(SequenceContext context, string id, SequenceActionResult param) { return SequenceAction.Ok(); } private static SequenceActionResult PlcIsDeactivating(SequenceContext context, string id, SequenceActionResult param) { // PLC 비활성화 상태 // IsActivated 가 false 가 될때까지 대기시키자... // 테스트: 약 1초 시간의 타임아웃으로 체크 var timeOutMS = 1000; if (_plc.WaitUntilDeactivated(timeOutMS, out int elapsedMS)) { Log.I($"PLC is deactivated: {elapsedMS} [ms] elapsed"); } else { Log.W($"PLC is deactivating: {elapsedMS} [ms] elapsed"); Thread.Sleep(SLEEP_TIME_MS); } return SequenceAction.Ok(); } private static SequenceActionResult GoToMonitorNetwork(SequenceContext context, string id, SequenceActionResult param) { #if DEBUG //Debug.WriteLine($"GO_MONITOR_NETWORK: --> MONITOR_NETWORK"); #endif Thread.Sleep(50); return SequenceAction.Ok(); } private static string MonitorPLCSettingStatus(SequenceContext context, string id, ICollection branches, SequenceActionResult param) { /* switch (Step_SETTING) { case eMainSeqStep.INIT: { Step_SETTING = eMainSeqStep.WORK; _latchForWriteBitUpdate = _plc.LatchForWriteBitUpdate; _plc.WriteBit((int)eBitOutput.EQP_SETTING_DATA_CHANGE_COMPLETE, false); //ST1_SETTING_COMPLETE // 대기함수 추가.. if (_plc.WaitWriteBitUpdate(_latchForWriteBitUpdate, out _) == false) { // 함수명은 기존 로거에서 지원하니, 라인번호만 찍자.. Log.W(_plc.WriteLogTag("WriteBit timed out")); } break; } case eMainSeqStep.WORK: { var res2 = _plc.ReadBit((int)eBitInput.EQP_SETTING_DATA_CHANGE_REQ); //ST1_SETTING_REQ // 비활성화와 비연결 예외를 구분해야 할 것 같은데...생각 좀 해보자... if (res2.err != E1.OK) { Step_SETTING = eMainSeqStep.INIT; swSetting.Stop(); Log.I($"Step_SETTING.eMainSeqStep.WORK 1: PLC is deactivating: {res2.err}"); return PLC_IS_DEACTIVATING; } else { if (res2.data) { // ZR 장치 읽기 var res = _plc.ReadPLCSettingData(); if (res != E1.OK) { // 비활성화에 의한 실패인지, 통신 오류에 의한 실패인지 구분 if (_plc._dataUpdateTimer.IsRunning) { Log.E("ReadPLCSettingData() is failed: --> reconnect"); // 원본과 다른 부분, 통신 실패로 재연결을 시도하니, 처음부터 다시해야 하지 않을까?? Step_SETTING = eMainSeqStep.INIT; swSetting.Stop(); return GO_RECONNECT_NETWORK; } else { Log.E("ReadPLCSettingData() is failed: PLC is deactivating"); Step_SETTING = eMainSeqStep.INIT; swSetting.Stop(); return PLC_IS_DEACTIVATING; } } _plc.Check_SettingData(); _latchForWriteBitUpdate = _plc.LatchForWriteBitUpdate; _plc.WriteBit((int)eBitOutput.EQP_SETTING_DATA_CHANGE_COMPLETE, true); //ST1_SETTING_COMPLETE // 대기함수 추가.. if (_plc.WaitWriteBitUpdate(_latchForWriteBitUpdate, out _) == false) { // 함수명은 기존 로거에서 지원하니, 라인번호만 찍자.. Log.W(_plc.WriteLogTag("WriteBit timed out")); } Step_SETTING = eMainSeqStep.ACK_WAIT; swSetting.Restart(); } } break; } case eMainSeqStep.ACK_WAIT: { var res2 = _plc.ReadBit((int)PLCController.eBitInput.EQP_SETTING_DATA_CHANGE_REQ); //ST1_SETTING_REQ if (res2.err != E1.OK) { Step_SETTING = eMainSeqStep.INIT; swSetting.Stop(); Log.I("Step_SETTING.eMainSeqStep.ACK: PLC is deactivating"); return PLC_IS_DEACTIVATING; } else { if (!res2.data) { Step_SETTING = eMainSeqStep.INIT; swSetting.Stop(); } else if (swSetting.ElapsedMilliseconds > TIME_OUT) { Step_SETTING = eMainSeqStep.INIT; EventLog.W($"PLC Setting Change Bit Clear TimeOver"); swSetting.Stop(); } } } break; } */ #if DEBUG //Debug.WriteLine($"MONITOR_SETTING: --> GO_MONITOR_NETWORK"); #endif return GO_MONITOR_NETWORK; } private static string MonitorPLCAlarmStatus(SequenceContext context, string id, ICollection branches, SequenceActionResult param) { /* switch (Step_ALARM) { case eMainSeqStep.INIT: { Step_ALARM = eMainSeqStep.WORK; _latchForWriteBitUpdate = _plc.LatchForWriteBitUpdate; _plc.WriteBit((int)eBitOutput.EQP_ALARM_STATUS_CHANGE_COMPLETE, false); // 대기함수 추가.. if (_plc.WaitWriteBitUpdate(_latchForWriteBitUpdate, out _) == false) { // 함수명은 기존 로거에서 지원하니, 라인번호만 찍자.. Log.W(_plc.WriteLogTag("WriteBit timed out")); } break; } case eMainSeqStep.WORK: { var res2 = _plc.ReadBit((int)eBitInput.EQP_ALARM_STATUS_CHANGE_REQ); if (res2.err != E1.OK) { Step_ALARM = eMainSeqStep.INIT; swAlarm.Stop(); Log.I("Step_ALARM.eMainSeqStep.WORK: PLC is deactivating"); return PLC_IS_DEACTIVATING; } else { if (res2.data) { // 알람 요청 // LUKAS: 꽤 많은 M 장치를 읽고 있음 var res = _plc.ReadPLCAlarm(); if (res != E1.OK) { // 비활성화에 의한 실패인지, 통신 오류에 의한 실패인지 구분 if (_plc._dataUpdateTimer.IsRunning) { Log.E("ReadPLCAlarm() is failed: --> reconnect"); // 원본과 다른 부분, 통신 실패로 재연결을 시도하니, 처음부터 다시해야 하지 않을까?? Step_ALARM = eMainSeqStep.INIT; swAlarm.Stop(); return GO_RECONNECT_NETWORK; } else { Log.E("ReadPLCAlarm() is failed: PLC is deactivating"); Step_ALARM = eMainSeqStep.INIT; swAlarm.Stop(); return PLC_IS_DEACTIVATING; } } //Thread.Sleep(50); // LUKAS: 순회를 하면서 로그를 남기는데...흠... _plc.CheckPLCAlarm(); _latchForWriteBitUpdate = _plc.LatchForWriteBitUpdate; _plc.WriteBit((int)eBitOutput.EQP_ALARM_STATUS_CHANGE_COMPLETE, true); // 대기함수 추가.. if (_plc.WaitWriteBitUpdate(_latchForWriteBitUpdate, out _) == false) { // 함수명은 기존 로거에서 지원하니, 라인번호만 찍자.. Log.W(_plc.WriteLogTag("WriteBit timed out")); } Step_ALARM = eMainSeqStep.ACK_WAIT; swAlarm.Restart(); } } break; } case eMainSeqStep.ACK_WAIT: { // LUKAS: 알람 요청이 없으면, Init (처음)으로 이동 var res2 = _plc.ReadBit((int)PLCController.eBitInput.EQP_ALARM_STATUS_CHANGE_REQ); if (res2.err != E1.OK) { Step_ALARM = eMainSeqStep.INIT; swAlarm.Stop(); Log.I("Step_ALARM.eMainSeqStep.ACK: PLC is deactivating"); return PLC_IS_DEACTIVATING; } else { if (!res2.data) { Step_ALARM = eMainSeqStep.INIT; swAlarm.Stop(); } else if (swAlarm.ElapsedMilliseconds > TIME_OUT) { // LUKAS: PLC 알람 리셋 타입오버 ?? Step_ALARM = eMainSeqStep.INIT; EventLog.W($"PLC ALARM Change Bit Clear TimeOver"); swAlarm.Stop(); } } break; } } */ #if DEBUG //Debug.WriteLine($"MONITOR_ALARM: --> MONITOR_SETTING"); #endif return MONITOR_SETTING; } private static string MonitorNetworkStatus(SequenceContext context, string id, ICollection branches, SequenceActionResult param) { // 통신 연결 상태를 검사한다. // 통신이 끊겼을 경우는 1. PLC를 비활성화했을 때, 2. 통신에러로 종료되었을 경우, // => Check Active 로 이동한다... // 1. => PLC 비활성화 // ==> Check Active 에서 처리됨... // 2. => 통신 재연결 로직 수행 // ==> 여기서 처리됨... if (_plc.IsConnected) { #if DEBUG /* // TEST CODE For TEST _plc.ReadBit(1); _plc.WriteBit(1, true); _plc.ReadWord(1); _plc.WriteWord(1, 1); */ //Debug.WriteLine($"MONITOR_NETWORK: --> CHECK_RUN_STATUS"); #endif return MONITOR_ALARM; } else { // 통신이 종료되었다... if (_plc._dataUpdateTimer.IsRunning == false) { // 비활성화 트리거링 Log.I("MonitorNetworkStatus(): PLC is deactivating"); return PLC_IS_DEACTIVATING; } // 통신 이상...!!! Log.I("Try to reconnect to PLC"); return GO_RECONNECT_NETWORK; } } private static string CheckActive(SequenceContext context, string id, ICollection branches, SequenceActionResult param) { while (!context.Stopped) { if (_plc.IsActivated) { // data 업데이트 스레드는 비동기로 동작중이다.. Log.I("PLC is activated: --> Monitor PLC Network state"); return MONITOR_NETWORK; } // 주기적 딜레이 Thread.Sleep(SLEEP_TIME_MS); } Log.W("Fatal error or Seq. Stop is occurred: --> Final"); return SeqNodeIds.FINAL; } private static SequenceActionResult Init(SequenceContext context, string id, SequenceActionResult param) { _plc = context.Devices[CommonDeviceIds.PLCDevice] as PLCController; //Log.I("PLC Mon. Sequence starts"); return SequenceAction.Ok(); } } }