2026-06-05 13:44:39 +09:00

323 lines
12 KiB
C#

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;
using System.Runtime.Remoting.Contexts;
using WeldingCommon.Utils;
using System.Threading;
using WeldingCommon.Alarm;
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();
DateTime Livetimer;
bool beforeLiveStatus = false;
bool updateLiveWait = false;
#region 생성자
IAuroraEnvironment _AuroraEnv;
public PLCController(string deviceId, ePLCNetType plcNetType, bool isSimul, PLCMMapFileType fileType, IAuroraEnvironment aurora)
: base(deviceId, isSimul)
{
_AuroraEnv = aurora;
// 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");
}
#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;
}
// Event Publisher 확인(이전 PLC에 있는 타이머 부분 추가)
_monitorPlcInterfaceMap.OnTimeout += MonitorPlcInterfaceMap;
}
public void MonitorPlcInterfaceMap()
{
// Live Bit Control
LiveStatusUpdate();
var isAutoRequest = ReadBit(eCommonBitInput.PLC_AUTO_REQ).data;
var isManualRequest = ReadBit(eCommonBitInput.PLC_MANUAL_REQ).data;
if (isAutoRequest && isManualRequest) {
Log.I("PLC 에서 Auto Run Start 와 Manual 전환 신호가 동시에 들어왔습니다. 신호를 무시합니다.");
return;
}
#region PLC 신호중에 Command 신호 들어온 것에 대한 처리. rising edge 인 경우에만 반응하도록 코딩할 것
if (ChangeChecker.IsChangedBoolMonitoring("MainSeqStart", isAutoRequest, MonitoringValue: true)) {
EventPublisher.Publish(new EventMessage("PLC", CommonEventIds.EVENT_AUTO_RUN_START_REQUEST));
}
//260512 잠시 주석처리
if (ChangeChecker.IsChangedBoolMonitoring("MainSeqStop", isManualRequest, MonitoringValue: true))
{
EventPublisher.Publish(new EventMessage("PLC", CommonEventIds.EVENT_AUTO_RUN_STOP_REQUEST));
}
var isRecipeUpdate = ReadBit(eCommonBitInput.PLC_RMS_PARAMETER_READ_REQUEST).data;
if (ChangeChecker.IsChangedBoolMonitoring("RmsParameterReadReq", isRecipeUpdate, MonitoringValue: true)) {
EventPublisher.Publish(new EventMessage("PLC", CommonEventIds.EVENT_RMS_PARAMETER_READ_REQUEST));
}
#endregion
}
private void LiveStatusUpdate()
{
if (IsSimul)
return;
// Live 신호 깜빡이기
// PLC 상태 변경 후 특정 시간동안 대기 하다가 값 넘겨주기
if (updateLiveWait) {
if (DateTime.Now.Subtract(Livetimer).TotalMilliseconds > 1000) {
updateLiveWait = false;
WriteBit(eCommonBitOutput.PC_LIVE_STATUS, beforeLiveStatus);
}
} else {
bool isLive = ReadBit(eCommonBitInput.PLC_LIVE_STATUS).data;
// PLC 상태가 변한경우
if (isLive != beforeLiveStatus) {
Livetimer = DateTime.Now;
beforeLiveStatus = isLive;
updateLiveWait = true;
}
// 10초간 PLC 상태가 변하지 않는 경우 TimeoOut 10000
//260512 주석 처리 잠시
if (DateTime.Now.Subtract(Livetimer).TotalMilliseconds > 10000)
{
AlarmManager.Alarm(AlarmCode.ALARM_DEVICE_PLC_DISCONNECT);
//Log.E($"PLC HeartBeat Signal Is Not Working");
}
}
}
public void RegisterMotion()
{
#region PLC 모션축 세팅
if (HasMotionAxes && _plcMotorList.Count == 0) {
// 모션축 등록.... (수정필요)
//_plcMotorList = new List<PLCMotorBase>();
//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;
// MicroTimer 설정
_monitorPlcInterfaceMap.UpdateRateUS = 100 * 1000;
_monitorPlcInterfaceMap.Start();
var res = base.DoActivate(config);
//Activate할 때, 한번 더 주소 맵핑 필요 (맵핑이 생성자에서 통신 세팅보다 느림)
LoadMemoryMapFromFiles();
if (res != E1.OK) { // PLC 통신 실패
return res;
}
beforeLiveStatus = false;
updateLiveWait = false;
return res;
}
protected override int DoDeactivate()
{
var res = base.DoDeactivate();
_monitorPlcInterfaceMap.Stop();
// 비동기 통신이 종료되었는지 확인하는 로직으로 교체
if (!WaitUntilUpdateTerminateMonitor(1000, out int elapsedMSForUpdateTerminate))
{ // 업데이트 스레드 종료가 아직 안됨
Log.E("Data update is not teminated yet");
return E2.CANCELLED;
}
if (res != E1.OK) {
Log.E("Deactivate is failed");
return res;
}
return res;
}
public bool WaitUntilUpdateTerminateMonitor(int timeOutMS, out int elapsedMS)
{
elapsedMS = timeOutMS;
// convert to Max count
var delayMS = 10;
int maxCount = timeOutMS / delayMS;
int i;
for (i = 0; i < maxCount; i++)
{
if (_monitorPlcInterfaceMap.IsRunning)
{ // 대기
// 짧은 주기로 검사하자 !!
Thread.Sleep(delayMS);
continue;
}
else
{ // Update thread is terminated
elapsedMS = i * delayMS;
return true;
}
}
// 실패
return false;
}
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