439 lines
18 KiB
C#
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 사용 방법
|
|
protected 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 |