using Aurora.Api; using Hermes; using System; using System.Diagnostics; using System.Threading; using static AuroraDeviceCommon.Utils.Utils; using static AuroraPLC.PLCBase; namespace AuroraPLC { // PLCMotorBase 용 public interface IPLCBase { (int err, bool data) ReadBit(int addrOffset, ePLCDevice device = ePLCDevice.B, bool isInput = true); (int err, int data) ReadDWord(int addrOffset, ePLCDevice device = ePLCDevice.W, bool isInput = true); int WriteBit(int addrOffset, bool onOff, ePLCDevice device = ePLCDevice.B, bool isOutput = true); int WriteWord(int addrOffset, short shData, ePLCDevice device = ePLCDevice.W, bool isOutput = true); int WriteDWord(int addrOffset, int iVal, ePLCDevice device = ePLCDevice.W, bool isOutput = true); bool WaitWriteWordUpdate(ushort latchedUpdatePointer, ulong latchedUpdateCycle, out int elapsedMS, int timeOutMS = 1000); ushort LatchForUpdatePointer { get; } ulong LatchForUpdateCycle { get; } //IAlarmService AlarmService { get; } } public abstract partial class PLCBase : IPLCBase { #region PLC 통신 메서드 (Aurora 용: 내부 버퍼에 read/write) //--------------------------------------- // Read //--------------------------------------- /// /// Read PLC Bit /// ** Read 이므로, 기본값은 isInput = true 임 /// /// /// /// bit value public (int err, bool data) ReadBit(int addrOffset, ePLCDevice device = ePLCDevice.B, bool isInput = true) { if (!IsActivated) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not activated")); return (E2.NOT_READY, false); } if (!IsConnected) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not connected")); return (E2.NOT_CONNECTED, false); } (int err, bool data) res2; if (isInput) { if (device == ePLCDevice.M) { res2 = ReadBit(MBitInputBlock, addrOffset); } else { res2 = ReadBit(BitInputBlock, addrOffset); } if (res2.err == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"In: Out of Range: 0x{addrOffset.ToString("X4")}")); } } else { if (device == ePLCDevice.M) { res2 = ReadBit(MBitOutputBlock, addrOffset); } else { res2 = ReadBit(BitOutputBlock, addrOffset); } if (res2.err == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"Out: Out of Range: 0x{addrOffset.ToString("X4")}")); } } return res2; } /// /// Read PLC Bit /// ** 위 기본형 ReadBit와 동일한 함수를 return type만 다른 방식으로 제공 /// ** Read 이므로, 기본값은 isInput = true 임 /// /// /// /// /// public int ReadBit(int addrOffset, out bool data, ePLCDevice device = ePLCDevice.B, bool isInput = true) { var result = ReadBit(addrOffset, device, isInput); data = result.data; return result.err; } /// /// Read PLC Word /// ** Read 이므로, 기본값은 isInput = true 임 /// /// /// /// public (int err, short data) ReadWord(int addrOffset, ePLCDevice device = ePLCDevice.W, bool isInput = true) { // 예외처리를 여기에 고정해야함 // 예외일 때 리턴값을 어떻게 하냐가 고민인데, 우선은 false or 0 or null 을 리턴함 if (!IsActivated) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not activated")); return (E2.NOT_READY, 0); } if (!IsConnected) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not connected")); return (E2.NOT_CONNECTED, 0); } (int err, short data) res2; if (isInput) { if (device == ePLCDevice.D) { res2 = ReadWord(DWordInputBlock, addrOffset); } else { res2 = ReadWord(WordInputBlock, addrOffset); } if (res2.err == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"In: Out of Range: 0x{addrOffset.ToString("X4")}")); } } else { if (device == ePLCDevice.D) { res2 = ReadWord(DWordOutputBlock, addrOffset); } else { res2 = ReadWord(WordOutputBlock, addrOffset); } if (res2.err == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"Out: Out of Range: 0x{addrOffset.ToString("X4")}")); } } return res2; } /// /// Read PLC Word /// ** Read 이므로, 기본값은 isInput = true 임 /// ** 위 기본형 ReadWord와 동일한 함수를 return type만 다른 방식으로 제공 /// /// /// /// public int ReadWord(int addrOffset, out short data, ePLCDevice device = ePLCDevice.W, bool isInput = true) { var result = ReadWord(addrOffset, device, isInput); data = result.data; return result.err; } /// /// Read PLC DWord /// ** Read 이므로, 기본값은 isInput = true 임 /// /// /// /// public (int err, int data) ReadDWord(int addrOffset, ePLCDevice device = ePLCDevice.W, bool isInput = true) { if (!IsActivated) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not activated")); return (E2.NOT_READY, 0); } if (!IsConnected) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not connected")); return (E2.NOT_CONNECTED, 0); } // 32bit word: lower, upper 순서 (int err, short[] data) res2; if (isInput) { if (device == ePLCDevice.D) { res2 = ReadWordBlock(DWordInputBlock, addrOffset, 2); } else { res2 = ReadWordBlock(WordInputBlock, addrOffset, 2); } if (res2.err == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"In: Out of Range: 0x{addrOffset.ToString("X4")}")); } } else { if (device == ePLCDevice.D) { res2 = ReadWordBlock(DWordOutputBlock, addrOffset, 2); } else { res2 = ReadWordBlock(WordOutputBlock, addrOffset, 2); } if (res2.err == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"Out: Out of Range: 0x{addrOffset.ToString("X4")}")); } } // word = 16 bit, upper word + lower word // 상위 워드로 이동, lower word 와 비트 or 조합 int outValue = (res2.data[1] << 16) | (res2.data[0] & 0x0000FFFF); return (res2.err, outValue); } /// /// Read PLC DWord /// ** Read 이므로, 기본값은 isInput = true 임 /// ** 위 기본형 ReadDWord와 동일한 함수를 return type만 다른 방식으로 제공 /// /// /// /// public int ReadDWord(int addrOffset, out int data, ePLCDevice device = ePLCDevice.W, bool isInput = true) { var result = ReadDWord(addrOffset, device, isInput); data = result.data; return result.err; } /// /// Read PLC DWord (+ Decimal Scale 지정) /// ** Read 이므로, 기본값은 isInput = true 임 /// /// /// /// /// public (int err, double data) ReadDWord(int addrOffset, double scaleDecimalPt, ePLCDevice device = ePLCDevice.W, bool isInput = true) { if (!IsActivated) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not activated")); return (E2.NOT_READY, 0); } if (!IsConnected) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not connected")); return (E2.NOT_CONNECTED, 0); } // 메서드 의미 // - 2워드 데이터를 upper/lower 로 32bit 정수형 데이터로 변환 // - scaleDecimalPt 을 이용하여, 실수형으로 변환 // 예외처리 if (Math.Abs(scaleDecimalPt) < 0.001) { Log.W($"Invalid denominator(zero divide): scaleDecimalPt: {scaleDecimalPt}"); return (E2.INVALID_ARGUMENT, 0); } // 32bit word: lower, upper 순서 (int err, short[] data) res2; if (isInput) { if (device == ePLCDevice.D) { res2 = ReadWordBlock(DWordInputBlock, addrOffset, 2); } else { res2 = ReadWordBlock(WordInputBlock, addrOffset, 2); } if (res2.err == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"In: Out of Range: 0x{addrOffset.ToString("X4")}")); } } else { if (device == ePLCDevice.D) { res2 = ReadWordBlock(DWordOutputBlock, addrOffset, 2); } else { res2 = ReadWordBlock(WordOutputBlock, addrOffset, 2); } if (res2.err == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"Out: Out of Range: 0x{addrOffset.ToString("X4")}")); } } // word = 16 bit // 상위 워드로 이동 int dwordValue = (res2.data[1] << 16) | (res2.data[0] & 0x0000FFFF); double outValue = dwordValue / scaleDecimalPt; return (res2.err, outValue); } /// /// Read PLC Word Block (Word를 특정 size만큼 읽기) /// ** Read 이므로, 기본값은 isInput = true 임 /// /// /// /// /// public (int err, short[] data) ReadWordBlock(int addrOffset, int wordSize, ePLCDevice device = ePLCDevice.W, bool isInput = true) { if (!IsActivated) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not activated")); return (E2.NOT_READY, null); } if (!IsConnected) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not connected")); return (E2.NOT_CONNECTED, null); } (int err, short[] data) res2; if (isInput) { if (device == ePLCDevice.D) { res2 = ReadWordBlock(DWordInputBlock, addrOffset, wordSize); } else { res2 = ReadWordBlock(WordInputBlock, addrOffset, wordSize); } if (res2.err == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"In: Out of Range: 0x{addrOffset.ToString("X4")}")); } } else { if (device == ePLCDevice.D) { res2 = ReadWordBlock(DWordOutputBlock, addrOffset, wordSize); } else { res2 = ReadWordBlock(WordOutputBlock, addrOffset, wordSize); } if (res2.err == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"Out: Out of Range: 0x{addrOffset.ToString("X4")}")); } } return res2; } public int ReadWordBlock(int addrOffset, int wordSize, out short[] data, ePLCDevice device = ePLCDevice.W, bool isInput = true) { var result = ReadWordBlock(addrOffset, wordSize, device, isInput); data = result.data; return result.err; } //====================================== //--------------------------------------- // Write //--------------------------------------- /// /// Write PLC Bit /// ** Write 이므로, 기본값은 isOutput = true 임 /// /// /// /// /// public int WriteBit(int addrOffset, bool onOff, ePLCDevice device = ePLCDevice.B, bool isOutput = true) { if (!IsActivated) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not activated")); return E2.NOT_READY; } if (!IsConnected) { if (IsLogModePLCDetail) Log.E(WriteLogTag("PLC is not connected")); return E2.NOT_CONNECTED; } int res; if (isOutput) { // Output if (device == ePLCDevice.M) { res = WriteBit(MBitOutputBlock, addrOffset, onOff); } else { res = WriteBit(BitOutputBlock, addrOffset, onOff); } if (res == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"Out: Out of Range: 0x{addrOffset.ToString("X4")}")); return res; } } else { // Input if (IsSimul != true) { // 에러 Log.E(WriteLogTag("Improper Write BitInput operation in real mode")); return E2.UNAVAILABLE; } if (device == ePLCDevice.M) { res = WriteBit(MBitInputBlock, addrOffset, onOff); } else { res = WriteBit(BitInputBlock, addrOffset, onOff); } if (res == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"In: Out of Range: 0x{addrOffset.ToString("X4")}")); return res; } } return E1.OK; } /// /// Write PLC Word /// ** Write 이므로, 기본값은 isOutput = true 임 /// /// /// /// /// public int WriteWord(int addrOffset, short shData, ePLCDevice device = ePLCDevice.W, bool isOutput = true) // 뮤텍스 필요 { if (!IsActivated) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not activated")); return E2.NOT_READY; } if (!IsConnected) { if (IsLogModePLCDetail) Log.E(WriteLogTag("PLC is not connected")); return E2.NOT_CONNECTED; } int res; if (isOutput) { if (device == ePLCDevice.D) { res = WriteWord(DWordOutputBlock, addrOffset, shData); } else { res = WriteWord(WordOutputBlock, addrOffset, shData); } if (res == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"Out: Out of Range: 0x{addrOffset.ToString("X4")}")); return res; } } else { if (IsSimul != true) { // 에러 Log.E(WriteLogTag("Improper Write WordInput operation in real mode")); return E2.UNAVAILABLE; } if (device == ePLCDevice.D) { res = WriteWord(DWordInputBlock, addrOffset, shData); } else { res = WriteWord(WordInputBlock, addrOffset, shData); } if (res == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"In: Out of Range: 0x{addrOffset.ToString("X4")}")); return res; } } return E1.OK; } /// /// Write PLC DWord /// ** Write 이므로, 기본값은 isOutput = true 임 /// /// /// /// public int WriteDWord(int addrOffset, int iVal, ePLCDevice device = ePLCDevice.W, bool isOutput = true) { if (!IsActivated) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not activated")); return E2.NOT_READY; } if (!IsConnected) { if (IsLogModePLCDetail) Log.E(WriteLogTag("PLC is not connected")); return E2.NOT_CONNECTED; } short[] wordOuput = new short[2]; wordOuput[0] = (short)(iVal & 0x0000FFFF); // lower word wordOuput[1] = (short)((iVal >> 16) & 0x0000FFFF); // upper word (signed 의 경우, MSB 가 1 이면, 왼쪽에 1 이 채워짐) int res; if (isOutput) { if (device == ePLCDevice.D) { res = WriteWordBlock(DWordOutputBlock, addrOffset, 2, wordOuput); } else { res = WriteWordBlock(WordOutputBlock, addrOffset, 2, wordOuput); } if (res == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"Out: Out of Range: 0x{addrOffset.ToString("X4")}")); return res; } } else { if (device == ePLCDevice.D) { res = WriteWordBlock(DWordInputBlock, addrOffset, 2, wordOuput); } else { res = WriteWordBlock(WordInputBlock, addrOffset, 2, wordOuput); } if (res == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"In: Out of Range: 0x{addrOffset.ToString("X4")}")); return res; } } return E1.OK; } /// /// Write PLC Word Block (Word를 특정 size만큼 쓰기) /// ** Write 이므로, 기본값은 isOutput = true 임 /// /// /// /// /// /// public int WriteWordBlock(int addrOffset, int wordSize, short[] shArray, ePLCDevice device = ePLCDevice.W, bool isOutput = true) { if (!IsActivated) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not activated")); return E2.NOT_READY; } if (!IsConnected) { if (IsLogModePLCDetail) Log.E(WriteLogTag("PLC is not connected")); return E2.NOT_CONNECTED; } int res; if (isOutput) { if (device == ePLCDevice.D) { res = WriteWordBlock(DWordOutputBlock, addrOffset, wordSize, shArray); } else { res = WriteWordBlock(WordOutputBlock, addrOffset, wordSize, shArray); } if (res == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"Out: Out of Range: 0x{addrOffset.ToString("X4")} to {wordSize}")); return res; } } else { // Input: 시뮬에서만 write 허용 if (IsSimul != true) { // 에러 Log.E(WriteLogTag("Improper Write WordInput operation in real mode")); return E2.UNAVAILABLE; } if (device == ePLCDevice.D) { res = WriteWordBlock(DWordInputBlock, addrOffset, wordSize, shArray); } else { res = WriteWordBlock(WordInputBlock, addrOffset, wordSize, shArray); } if (res == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"In: Out of Range: 0x{addrOffset.ToString("X4")} to {wordSize}")); return res; } } return E1.OK; } // For atomic (?) operation // 근접한, but 양립불가한 Bit 메모리주소를 한번에 Read/Write 하고 싶을 때 // BitBlock API추가 (2가지 버전, bool 배열, ushort 값(비트 단위, 16bit)) // 기본: 이건 bool 배열이므로, size 에 제약 없음 public (int err, bool[] data) ReadBitBlock(int addrOffset, int bitSize, ePLCDevice device = ePLCDevice.B, bool isInput = true) { if (!IsActivated) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not activated")); return (E2.NOT_READY, null); } if (!IsConnected) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not connected")); return (E2.NOT_CONNECTED, null); } (int err, bool[] data) res2; if (isInput) { if (device == ePLCDevice.M) { res2 = ReadBitBlock(MBitInputBlock, addrOffset, bitSize); } else { res2 = ReadBitBlock(BitInputBlock, addrOffset, bitSize); } if (res2.err == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"In: Out of Range: 0x{addrOffset.ToString("X4")}")); } } else { if (device == ePLCDevice.M) { res2 = ReadBitBlock(MBitOutputBlock, addrOffset, bitSize); } else { res2 = ReadBitBlock(BitOutputBlock, addrOffset, bitSize); } if (res2.err == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"Out: Out of Range: 0x{addrOffset.ToString("X4")}")); } } return res2; } // 옵션: 비트 데이터이므로, ushort 를 사용함 (bitSize : (0, 16]) public (int err, ushort data) ReadBitBlockVal(int addrOffset, int bitSize, ePLCDevice device = ePLCDevice.B, bool isInput = true) { if (!IsActivated) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not activated")); return (E2.NOT_READY, 0); } if (!IsConnected) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not connected")); return (E2.NOT_CONNECTED, 0); } (int err, bool[] data) res2Arr; if (isInput) { if (device == ePLCDevice.M) { res2Arr = ReadBitBlock(MBitInputBlock, addrOffset, bitSize); } else { res2Arr = ReadBitBlock(BitInputBlock, addrOffset, bitSize); } if (res2Arr.err == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"In: Out of Range: 0x{addrOffset.ToString("X4")}")); } } else { if (device == ePLCDevice.M) { res2Arr = ReadBitBlock(MBitOutputBlock, addrOffset, bitSize); } else { res2Arr = ReadBitBlock(BitOutputBlock, addrOffset, bitSize); } if (res2Arr.err == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"Out: Out of Range: 0x{addrOffset.ToString("X4")}")); } } // RW 는 PLC 장치에서 매우 중요한 기능 // RW API 를 잘못 사용할 경우, 장비 세팅과정에서 드러날건데, 문제가 바로 드러나는게 디버깅에 좋을 듯 // 잘못된 주소 범위가 입력되면, data 값이 null 임 // ==> 아래 bool 배열 -> ushort 변환할 필요가 없으므로, 리턴하도록 하자 if (res2Arr.err == E2.INVALID_ARGUMENT) { return (res2Arr.err, 0); } // 예외 체크 : 최대 16 bit 까지만 읽는것을 허용 // 내부에 1 보다 작은지 검사하는 로직이 있음 if (bitSize > 16) { // 에러 Log.E(WriteLogTag($"Invalid size({bitSize}): must be <= 16")); // 해당 에러코드는 리턴하되, 일단은 16으로 제약을 건다. res2Arr.err = (int)ePLCErrCode.API_Read_InvalidSize; bitSize = 16; } (int err, ushort data) res2 = (res2Arr.err, 0); // bool 배열을 비트 순서별로 data 로 변환하기 for (int i = 0; i < bitSize; i++) { // bool 배열을 자리수에 맞춰서 ushort 로 조합 res2.data += (ushort)((res2Arr.data[i] ? 1 : 0) * (1 << i)); } return res2; } // 기본 public int WriteBitBlock(int addrOffset, int bitSize, bool[] bArray, ePLCDevice device = ePLCDevice.B, bool isOutput = true) { if (!IsActivated) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not activated")); return E2.NOT_READY; } if (!IsConnected) { if (IsLogModePLCDetail) Log.E(WriteLogTag("PLC is not connected")); return E2.NOT_CONNECTED; } int res; if (isOutput) { if (device == ePLCDevice.M) { res = WriteBitBlock(MBitOutputBlock, addrOffset, bitSize, bArray); } else { res = WriteBitBlock(BitOutputBlock, addrOffset, bitSize, bArray); } if (res == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"Out: Out of Range: 0x{addrOffset.ToString("X4")} to {bitSize}")); return res; } } else { // Input: 시뮬에서만 write 허용 if (IsSimul != true) { // 에러 Log.E(WriteLogTag("Improper Write BitInput block operation in real mode")); return E2.UNAVAILABLE; } if (device == ePLCDevice.M) { res = WriteBitBlock(MBitInputBlock, addrOffset, bitSize, bArray); } else { res = WriteBitBlock(BitInputBlock, addrOffset, bitSize, bArray); } if (res == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"In: Out of Range: 0x{addrOffset.ToString("X4")} to {bitSize}")); return res; } } return E1.OK; } // 옵션: 비트 처리는 unsigned 사용 public int WriteBitBlock(int addrOffset, int bitSize, ushort ushData, ePLCDevice device = ePLCDevice.B, bool isOutput = true) { if (!IsActivated) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not activated")); return E2.NOT_READY; } if (!IsConnected) { if (IsLogModePLCDetail) Log.E(WriteLogTag("PLC is not connected")); return E2.NOT_CONNECTED; } int res = E1.OK, resByBitSize = 0; // 예외 체크 : ushort 를 사용하므로, 16 bit 까지만 허용됨 // 내부에 1 보다 작은지 검사하는 로직이 있음 if (bitSize > 16) { // 에러 Log.E(WriteLogTag($"Invalid size({bitSize}): must be <= 16")); // 에러코드는 리턴하되, 일단은 16으로 제약을 건다. resByBitSize = (int)ePLCErrCode.API_Write_InvalidSize; bitSize = 16; } // shData 를 bool 배열로 변환 bool[] bArray = new bool[bitSize]; for (int i = 0; i < bitSize; i++) { bArray[i] = ((ushData >> i) & 0x1) == 1 ? true : false; } if (isOutput) { if (device == ePLCDevice.M) { res = WriteBitBlock(MBitOutputBlock, addrOffset, bitSize, bArray); } else { res = WriteBitBlock(BitOutputBlock, addrOffset, bitSize, bArray); } if (res == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"Out: Out of Range: 0x{addrOffset.ToString("X4")} to {bitSize}")); return res; } } else { // Input: 시뮬에서만 write 허용 if (IsSimul != true) { // 에러 Log.E(WriteLogTag("Improper Write BitInput block operation in real mode")); return E2.UNAVAILABLE; } if (device == ePLCDevice.M) { res = WriteBitBlock(MBitInputBlock, addrOffset, bitSize, bArray); } else { res = WriteBitBlock(BitInputBlock, addrOffset, bitSize, bArray); } if (res == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"In: Out of Range: 0x{addrOffset.ToString("X4")} to {bitSize}")); return res; } } return res + resByBitSize; } //=========================================== #endregion #region 특수 케이스 // B장치 초기화 (모두 0 으로 reset) public void AllBitClear(ePLCDevice device = ePLCDevice.B, bool isOutput = true) { lock (_lockBitInOut) { if (isOutput) { // 모든 B 출력 초기화 if(device == ePLCDevice.M) { for (int i = 0; i < MBitOutputBlock.Count; i++) MBitOutputBlock.Data[i] = false; } else { for (int i = 0; i < BitOutputBlock.Count; i++) BitOutputBlock.Data[i] = false; } } else { // 모든 B 입력 초기화 if(device == ePLCDevice.M) { for (int i = 0; i < MBitInputBlock.Count; i++) MBitInputBlock.Data[i] = false; } else { for (int i = 0; i < BitInputBlock.Count; i++) BitInputBlock.Data[i] = false; } } } } // 수동 업데이트 (주의: 업데이트 스레드 시작 전에만 사용. 최초 업데이트 수행) protected void ManualDataUpdate() { DataUpdator(); } #endregion #region 주로 모니터링 뷰에서 사용 // 모니터링 뷰에서 필요한 함수 public eSendResult WriteWordBlockForAscii(int addrOffset, int sendWordSize, ref string[] strData, ePLCDevice device = ePLCDevice.W, bool isOutput = true) { // 1 워드에 최대 2개의 문자 저장/표현 가능 char[] charTemp = new char[sendWordSize * 2]; short[] sendData = new short[sendWordSize]; try { // 문자열에서 한 문자씩 char 배열로 분리 for (int i = 0; i < strData[0].Length; i++) { charTemp[i] = Convert.ToChar(strData[0].Substring(i, 1)); } // 기본 워드단위로 PLC와 통신함 int j = 0; for (int i = 0; i < sendWordSize; i++) { // 2문자 단위로, 1 워드에 저장하자... j = i * 2; // 미츠비시 PLC는 lower word -> upper word 순으로 주소값이 순차 증가함 // 2문자 단위로 워드에 저장.. string strTemp = string.Format("{0:X2}", Convert.ToInt16(charTemp[j + 1])) + string.Format("{0:X2}", Convert.ToInt16(charTemp[j])); sendData[i] = Int16.Parse(strTemp, System.Globalization.NumberStyles.AllowHexSpecifier); } int iRet; if (isOutput) { if(device == ePLCDevice.D) { iRet = WriteWordBlock(DWordOutputBlock, addrOffset, sendWordSize, sendData); } else { iRet = WriteWordBlock(WordOutputBlock, addrOffset, sendWordSize, sendData); } if (iRet == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"Out: Out of Range: 0x{addrOffset.ToString("X4")}")); } } else { // Input: 시뮬에서만 writing 허용 if (IsSimul) { if(device == ePLCDevice.D) { iRet = WriteWordBlock(DWordInputBlock, addrOffset, sendWordSize, sendData); } else { iRet = WriteWordBlock(WordInputBlock, addrOffset, sendWordSize, sendData); } if (iRet == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"In: Out of Range: 0x{addrOffset.ToString("X4")}")); } } else { // 잘못된 사용... Log.E(WriteLogTag("Write WordInput in Real mode is wrong")); return eSendResult.Error_Exception; } } if (iRet != E1.OK) return eSendResult.Error_Communication; return eSendResult.OK; } catch (Exception ex) { Log.E($"Exception: {ex.Message}"); return eSendResult.Error_Exception; } } // 모니터링뷰에서만 사용함...개선 검토: 중복 여부 등 public eSendResult WriteWordBlock(eDataType displayType, int addrOffset, int nCount, in string[] strData, ePLCDevice device = ePLCDevice.W, bool isHex = false, bool isOutput = true) { if (!IsActivated) { if (IsLogModePLCDetail) Log.W(WriteLogTag("PLC is not activated")); return eSendResult.Error_NotActivated; } if (!IsConnected) { if (IsLogModePLCDetail) Log.E(WriteLogTag("PLC is not connected")); return eSendResult.Error_NotConnect; } int Index = 0; // 16bit = 0, 32bit = 1, Ascii = 2 // 보낼 개수 * 워드 단위 (16비트 or 32비트) // 워드 기준 총 사이즈 int iSendLength = nCount * ((int)displayType + 1); short[] iSendData = new short[iSendLength]; // 메모리 할당 int iRet; bool bRet; string strTemp = ""; // try-catch 가 여기서 필요한가?? 통신 API 는 별도의 스레드에서 전담할건데.. try { // 변환 로직 switch (displayType) { case eDataType.Type_16Bit: { // short 기준 보낼 데이터 수 for (int i = 0; i < nCount; ++i) { // 문자열 숫자를 16비트 정수형으로 변환 if (!isHex) { // 십진수 문자열 // 숫자 문자열인지 검사 if (!IsNumeric(ref strData[i])) return eSendResult.Error_DataIsNumeric; // 실수이면 에러처리... if (!Int32.TryParse(strData[i], out int sendData)) { return eSendResult.Error_DataType; }; if ((sendData > Int16.MaxValue) || (sendData < Int16.MinValue)) return eSendResult.Error_DataRange; iSendData[i] = (short)sendData; } else { // 헥사 문자열 if (strData.Length > 4) return eSendResult.Error_DataLength; bRet = Int16.TryParse(strData[i], System.Globalization.NumberStyles.AllowHexSpecifier, null, out short sendData); if (!bRet) return eSendResult.Error_DataType; iSendData[i] = (short)sendData; } } break; } case eDataType.Type_32Bit: { for (int i = 0; i < nCount; ++i) { if (!isHex) { // 정수형 문자열 // 숫자인지 검사 if (!IsNumeric(ref strData[i])) return eSendResult.Error_DataIsNumeric; // 실수이면 에러처리... Int32 범위 초과와 구분하기위해서 Int64로 검사함 if (!Int64.TryParse(strData[i], out var sendData)) { return eSendResult.Error_DataType; }; // 32비트 범위 유효 검사 if ((sendData > Int32.MaxValue) || (sendData < Int32.MinValue)) return eSendResult.Error_DataRange; // 32 비트는 4 바이트 ==> 헥사값으로 8개 (1바이트는 헥사 2개로 표현) strTemp = string.Format("{0:X8}", int.Parse(strData[i])); // 정수값을 헥사 문자열로 변환 (8개 헥사문자열) iSendData[Index] = Int16.Parse(StrRight(strTemp, 4), System.Globalization.NumberStyles.AllowHexSpecifier); // lower part Index++; iSendData[Index] = Int16.Parse(StrLeft(strTemp, 4), System.Globalization.NumberStyles.AllowHexSpecifier); // upper part Index++; } else { // 헥사 문자열 if (strData.Length > 8) return eSendResult.Error_DataLength; // 실수이면 에러처리... Int32 비트초과와 구분하기위해서 Int64로 검사함 if (!Int64.TryParse(strData[i], System.Globalization.NumberStyles.AllowHexSpecifier, null, out _)) { return eSendResult.Error_DataType; }; // 문자열이 작을 시, 0을 패딩하여, 8자리 문자열로 만들기.. strTemp = strData[i].PadLeft(8, '0'); iSendData[Index] = Int16.Parse(StrRight(strTemp, 4), System.Globalization.NumberStyles.AllowHexSpecifier); Index++; iSendData[Index] = Int16.Parse(StrLeft(strTemp, 4), System.Globalization.NumberStyles.AllowHexSpecifier); Index++; } } break; } } if (isOutput) { if(device == ePLCDevice.D) { iRet = WriteWordBlock(DWordOutputBlock, addrOffset, (short)iSendLength, iSendData); } else { iRet = WriteWordBlock(WordOutputBlock, addrOffset, (short)iSendLength, iSendData); } if (iRet == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"Out: Out of Range: 0x{addrOffset.ToString("X4")}")); } } else { // Input: 시뮬에서만 write 허용 if (IsSimul) { if(device == ePLCDevice.D) { iRet = WriteWordBlock(DWordInputBlock, addrOffset, (short)iSendLength, iSendData); } else { iRet = WriteWordBlock(WordInputBlock, addrOffset, (short)iSendLength, iSendData); } if (iRet == E2.INVALID_ARGUMENT) { Log.E(WriteLogTag($"In: Out of Range: 0x{addrOffset.ToString("X4")}")); } } else { // 잘못된 사용... Log.E(WriteLogTag("Write WordInput in Real mode is wrong")); return eSendResult.Error_Exception; } } if (iRet != E1.OK) return eSendResult.Error_Communication; return eSendResult.OK; } catch (Exception ex) { Log.E($"Exception: {ex.Message}"); return eSendResult.Error_Exception; } } #endregion #region PLC R/W 동기화용 public ushort LatchForUpdatePointer { get => PLCUpdatorInfo.UpdatePointer; } public ulong LatchForUpdateCycle { get => PLCUpdatorInfo.NumberOfUpdateCycle; } public bool WaitWriteBitUpdateUntil(int waitCycle, int timeOutMS, out int elapsedMS) { elapsedMS = timeOutMS; // convert to Max count int delayMS = (int)(CurConfigUpdateRateMS * 0.1); if (delayMS < minDelayMS) delayMS = minDelayMS; Stopwatch _sw = new Stopwatch(); _sw.Start(); if (waitCycle <= 0) { // 예외 처리 waitCycle = 1; } // latch var curUpdateCycle = PLCUpdatorInfo.NumberOfWriteBit + (ulong)waitCycle; while (_sw.ElapsedMilliseconds < timeOutMS) { // 타임아웃 이내인 경우, // 업데이트 카운트 체크 if (PLCUpdatorInfo.NumberOfWriteBit > curUpdateCycle) { elapsedMS = (int)_sw.ElapsedMilliseconds; // 타이머 종료 _sw.Stop(); return true; } // 딜레이.. Thread.Sleep(delayMS); } // 타임아웃... elapsedMS = (int)_sw.ElapsedMilliseconds; _sw.Stop(); return false; } // 비트 출력 이전 단계의 횟수를 저장!!! 즉 워드 출력임 public ulong LatchForWriteBitUpdate { get => PLCUpdatorInfo.NumberOfWriteWord; } public bool WaitWriteBitUpdate(ulong latchedWriteBitUpdate, out int elapsedMS, int timeOutMS = 1000) { elapsedMS = timeOutMS; // convert to Max count int delayMS = (int)(CurConfigUpdateRateMS * 0.1); if (delayMS < minDelayMS) delayMS = minDelayMS; Stopwatch _sw = new Stopwatch(); _sw.Start(); // latch // 애매한 상황: WriteWordOutputToPLC 후, WriteBitOutputToPLC 도중에 Write 가 이루어진 경우 // 즉, latchedWriteBitUpdate - numberOfWriteBit > 0 은 현재 WriteBitOutputToPLC 진행 중을 의미함 // => 애매하니(안정적으로) +1 을 추가함 var numberOfWriteBit = PLCUpdatorInfo.NumberOfWriteBit; var curUpdateCycle = (latchedWriteBitUpdate - numberOfWriteBit) > 0 ? numberOfWriteBit + 1 : numberOfWriteBit; while (_sw.ElapsedMilliseconds < timeOutMS) { // 타임아웃 이내인 경우, // 업데이트 카운트 체크 if (PLCUpdatorInfo.NumberOfWriteBit > curUpdateCycle) { elapsedMS = (int)_sw.ElapsedMilliseconds; // 타이머 종료 _sw.Stop(); return true; } // 딜레이.. Thread.Sleep(delayMS); } // 타임아웃... elapsedMS = (int)_sw.ElapsedMilliseconds; _sw.Stop(); return false; } /* // 내부 latch 부분 재검토... public bool WaitWriteBitUpdate(out int elapsedMS, int timeOutMS) { elapsedMS = timeOutMS; // convert to Max count int delayMS = (int)(curUpdateRateMS * 0.1); if (delayMS < 10) delayMS = 10; Stopwatch _sw = new Stopwatch(); _sw.Start(); // latch // ReadWordInputFromPLC 와 WriteBitOutputToPLC 사이인 경우, 애매하니, +1 을 한다. var curUpdateCycle = (PLCUpdateTimerInfo.NumberOfReadWord - PLCUpdateTimerInfo.NumberOfWriteBit) > 0 ? PLCUpdateTimerInfo.NumberOfWriteBit + 1 : PLCUpdateTimerInfo.NumberOfWriteBit; while (_sw.ElapsedMilliseconds < timeOutMS) { // 타임아웃 이내인 경우, // 업데이트 카운트 체크 if (PLCUpdateTimerInfo.NumberOfWriteBit > curUpdateCycle) { elapsedMS = (int)_sw.ElapsedMilliseconds; // 타이머 종료 _sw.Stop(); return true; } // 딜레이.. Thread.Sleep(delayMS); } // 타임아웃... elapsedMS = (int)_sw.ElapsedMilliseconds; _sw.Stop(); return false; } */ // 앞단계 PLC API 업데이트 횟수 public ulong LatchForReadWordUpdate { get => PLCUpdatorInfo.NumberOfWriteBit; } public bool WaitReadWordUpdate(ulong latchedReadWordUpdate, out int elapsedMS, int timeOutMS = timeOut1000MS) { elapsedMS = timeOutMS; // convert to Max count int delayMS = (int)(CurConfigUpdateRateMS * 0.1); if (delayMS < minDelayMS) delayMS = minDelayMS; // MX Component 기준.. Stopwatch _sw = new Stopwatch(); _sw.Start(); // latch // ReadBitInputFromPLC 와 ReadWordInputFromPLC 사이라면 +1 함 var numberOfReadWord = PLCUpdatorInfo.NumberOfReadWord; var curUpdateCycle = (latchedReadWordUpdate - numberOfReadWord) > 0 ? numberOfReadWord + 1 : numberOfReadWord; while (_sw.ElapsedMilliseconds < timeOutMS) { // 타임아웃 이내인 경우, // 업데이트 카운트 체크 if (PLCUpdatorInfo.NumberOfReadWord > curUpdateCycle) { elapsedMS = (int)_sw.ElapsedMilliseconds; // 타이머 종료 _sw.Stop(); return true; } // 딜레이.. Thread.Sleep(delayMS); } // 타임아웃... elapsedMS = (int)_sw.ElapsedMilliseconds; _sw.Stop(); return false; } public bool WaitReadWordUpdate(out int elapsedMS, int timeOutMS = timeOut1000MS) { elapsedMS = timeOutMS; // convert to Max count int delayMS = (int)(CurConfigUpdateRateMS * 0.1); if (delayMS < minDelayMS) delayMS = minDelayMS; Stopwatch _sw = new Stopwatch(); _sw.Start(); // latch // 애매한 상황: 동기화를 하는 순간에 워드 입력이 업데이트 중일때....이런 확률은 거의 없을 것 같은데... // 이전 단계인 비트 출력 횟수를 래치해야 할까??? // 일단은 이대로 해놓고 모니터링해보자... var curUpdateCycle = PLCUpdatorInfo.NumberOfReadWord; while (_sw.ElapsedMilliseconds < timeOutMS) { // 타임아웃 이내인 경우, // 업데이트 카운트 체크 if (PLCUpdatorInfo.NumberOfReadWord > curUpdateCycle) { elapsedMS = (int)_sw.ElapsedMilliseconds; // 타이머 종료 _sw.Stop(); return true; } // 딜레이.. Thread.Sleep(delayMS); } // 타임아웃... elapsedMS = (int)_sw.ElapsedMilliseconds; _sw.Stop(); return false; } // 보통 Write Bit 전에 필요한 데이터를 Write Word 로 전달함 // 따라서, WordOutput --> BitOutput 순서를 보장하기 위한 동기화 메서드임 public ulong LatchForWriteWordUpdate { get => PLCUpdatorInfo.NumberOfReadBit; } public bool WaitWriteWordUpdate(ushort latchedUpdatePointer, ulong latchedUpdateCycle, out int elapsedMS, int timeOutMS = timeOut1000MS) { // WO ==> BO 순서를 보장하는게 중요함 // 따라서, Write WO 완료시점을 기준으로 판단 // 노 대기 조건 (PLC 업데이트 스레드 기준) // 1. WO 전에 완료 (<= 4) // 2. BO 후에 완료 (>= 8) // 대기 조건 // 3. WO 도중 (5) // 4. BO 전 (6) // 5. BO 도중 (7) // ==> BO 완료까지 대기 // 노 대기 조건 if (latchedUpdatePointer <= 4 || latchedUpdatePointer == 8) { elapsedMS = 0; return true; } // 대기 조건 : BO 완료까지 대기 필요 // convert to Max count : Sleep 간격용 (PLC 업데이트 rate 기준, 얼마나 자주 조건 체크를 할건지) int delayMS = (int)(CurConfigUpdateRateMS * 0.1); // 10배 if (delayMS < minDelayMS) delayMS = minDelayMS; // 하한선 Stopwatch _sw = new Stopwatch(); _sw.Start(); while (_sw.ElapsedMilliseconds < timeOutMS) { // 타임아웃 이내인 경우, // 업데이트 카운트 체크 if (PLCUpdatorInfo.NumberOfUpdateCycle > latchedUpdateCycle) { elapsedMS = (int)_sw.ElapsedMilliseconds; // 타이머 종료 _sw.Stop(); return true; } // 딜레이.. Thread.Sleep(delayMS); } // 타임아웃... elapsedMS = (int)_sw.ElapsedMilliseconds; _sw.Stop(); return false; } public bool WaitWriteWordUpdate(out int elapsedMS, int timeOutMS = timeOut1000MS) { // 무조건 1번의 WriteWord 를 보장함.. // 주의 영역은 ReadBitInputFromPLC - WriteWordOutputToPLC 구간.. elapsedMS = timeOutMS; // convert to Max count int delayMS = (int)(CurConfigUpdateRateMS * 0.1); if (delayMS < minDelayMS) delayMS = minDelayMS; Stopwatch _sw = new Stopwatch(); _sw.Start(); // latch var curUpdateCycle = PLCUpdatorInfo.NumberOfWriteWord; while (_sw.ElapsedMilliseconds < timeOutMS) { // 타임아웃 이내인 경우, // 업데이트 카운트 체크 if (PLCUpdatorInfo.NumberOfWriteWord > curUpdateCycle) { elapsedMS = (int)_sw.ElapsedMilliseconds; // 타이머 종료 _sw.Stop(); return true; } // 딜레이.. Thread.Sleep(delayMS); } // 타임아웃... elapsedMS = (int)_sw.ElapsedMilliseconds; _sw.Stop(); return false; } #endregion } }