420 lines
19 KiB
C#
420 lines
19 KiB
C#
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 string _plcDeviceId;
|
|
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(string plcDeviceId)
|
|
{
|
|
_plcDeviceId = plcDeviceId;
|
|
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<string> 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<string> 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<string> 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<string> 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<string> 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<string> 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[_plcDeviceId] as PLCController;
|
|
//Log.I("PLC Mon. Sequence starts");
|
|
return SequenceAction.Ok();
|
|
}
|
|
}
|
|
}
|