2026-01-26 18:56:53 +09:00

439 lines
18 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;
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<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;
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