2026-01-26 14:51:28 +09:00

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();
}
}
}