using AuroraDeviceCommon.Utils; using Hermes; using System; using System.Collections.Generic; namespace AuroraPLC { public struct stPLCUpdatorInfo { // 데이터 업데이트 스레드 동작 여부 확인용 public ulong NumberOfUpdateCycle { get; set; } public ulong NumberOfReadBit { get; set; } public ulong NumberOfReadWord { get; set; } public ulong NumberOfWriteBit { get; set; } public ulong NumberOfWriteWord { get; set; } // 현재 어느 지점인지 확인용 public ushort UpdatePointer { get; set; } public ulong NumberOfRBFail { get; set; } public ulong NumberOfWBFail { get; set; } public ulong NumberOfRWFail { get; set; } public ulong NumberOfWWFail { get; set; } public ulong NumberOfReconnection { get; set; } public stPLCUpdatorInfo(int init = 0) { NumberOfUpdateCycle = NumberOfReadBit = NumberOfReadWord = NumberOfWriteBit = NumberOfWriteWord = NumberOfRBFail = NumberOfWBFail = NumberOfRWFail = NumberOfWWFail = NumberOfReconnection = (ulong)init; UpdatePointer = (ushort)init; } } public abstract partial class PLCBase { const int RWStart = 0; // RW 시작 const int OnReadWI = 1; // Read Word Input 중 const int AfterReadWI = 2; // Read Word Input 완료 const int OnReadBI = 3; // Read Bit Input 중 const int AfterReadBI = 4; // Read Bit Input 완료 const int OnWriteWO = 5; // Write Word Output 중 const int AfterWriteWO = 6; // Write Word Output 완료 const int OnWriteBO = 7; // Write Bit Output 중 const int RWEnd = 8; // RW 완료 //-------------------------------------------- // 수정 검토... public enum ePLCDevice { M = 4, D = 13, B = 23, // LB : Link relay W = 24, // LW : Link register ZR = 220 } private const char DevB = 'B'; private const char DevW = 'W'; private const char DevM = 'M'; private const char DevD = 'D'; //====================================================== private const int _wordSize = 16; // bit // 뮤텍스 : 현 구조상, Bit 와 Word 2개로만 구분함 private readonly object _lockBitInOut = new object(); private readonly object _lockWordInOut = new object(); // 송수신 워드 size public int BitInputWordSize { get; private set; } public int BitOutputWordSize { get; private set; } public int MBitInputWordSize { get; private set; } public int MBitOutputWordSize { get; private set; } public int WordInputWordSize { get; private set; } public int WordOutputWordSize { get; private set; } public int DWordInputWordSize { get; private set; } public int DWordOutputWordSize { get; private set; } // 기타: Bit 개수 public int BitInputSize { get; private set; } public int BitOutputSize { get; private set; } public int MBitInputSize { get; private set; } public int MBitOutputSize { get; private set; } //public double DataUpdateRateMS { get; private set; } public double DataUpdateRateUS { get; private set; } #region PLC 관련 메모리 버퍼 및 초기화 // PLC Bit/Word 메모리 관련 internal class IOBlock { internal int BaseAddress; // 시작 주소값 internal int Count; // 버퍼 범위 internal T[] Data; // 실제 data internal string DevAddr; // 미츠비시 PLC 의 Device type (ex. LB (23), LW (24), etc) internal void MakeDevAddr(char dev) { if (dev == 'D' || dev == 'M') { DevAddr = string.Format("{0}{1:D}", dev, BaseAddress); } else { DevAddr = string.Format("{0}{1:X}", dev, BaseAddress); } } } private IOBlock BitInputBlock; // Input: PC <-- PLC private IOBlock BitOutputBlock; // Output: PC --> PLC private IOBlock MBitInputBlock; private IOBlock MBitOutputBlock; private IOBlock WordInputBlock; private IOBlock WordOutputBlock; private IOBlock DWordInputBlock; private IOBlock DWordOutputBlock; private void InitBitBlock(IList<(int address, AddrCommentList descript)> config, IOBlock bitBlock, ePLCDevice device = ePLCDevice.B) { // 메모리 주소를 config에 반영시 반드시 정렬되어 있다고 가정, 따라서 정렬로직은 불필요... if (config.Count != 0) { var startAddress = config[0].address; // 시작주소는 16 배수여야 한다. 메모리 총합도 16 배수여야 함 bitBlock.BaseAddress = startAddress; bitBlock.Count = config.Count; } else { bitBlock.Count = 0; } // 버퍼 할당 bitBlock.Data = new bool[bitBlock.Count]; // 시작주소를 'B' + "HexAddr" 로 저장 if(device == ePLCDevice.M) { bitBlock.MakeDevAddr(DevM); } else { bitBlock.MakeDevAddr(DevB); } } private void InitWordBlock(IList<(int address, AddrCommentList descript)> config, IOBlock wordBlock, ePLCDevice device = ePLCDevice.W) { if (config.Count != 0) { //var startAddress = config.OrderBy(x => x.address).First().address; var startAddress = config[0].address; wordBlock.BaseAddress = startAddress; wordBlock.Count = config.Count; } else { wordBlock.Count = 0; } wordBlock.Data = new short[wordBlock.Count]; if(device == ePLCDevice.D) { wordBlock.MakeDevAddr(DevD); } else { wordBlock.MakeDevAddr(DevW); } } #endregion #region 유틸리티 private static bool InRange(IOBlock block, int addrOffset, int numOfData = 1) { // 예외 검사 if (numOfData <= 0) { return false; } // addrOffset: [0, block.Count) return addrOffset >= 0 && addrOffset + numOfData - 1 < block.Count; } private void BitArrayToWordArray(bool[] bitArray, short[] wordArray) { int wordIndex = 0; for (int j = 0; j < wordArray.Length; j++) { for (int k = 0; k < _wordSize; k++) { wordArray[j] |= (short)((bitArray[wordIndex + k] ? 1 : 0) << k); } wordIndex += _wordSize; } } /// /// 입력: wordArray 를 bitArray 에 저장한다. /// /// /// private void WordArrayToBitArray(short[] wordArray, bool[] bitArray) { int bitIndex = 0; for (int j = 0; j < wordArray.Length; j++) { for (int k = 0; k < _wordSize; k++) { // [0~15] : 워드 data (16 bit)를 각각 0번 bit 로 이동시켜서 bit 검사 및 저장하는 로직 bitArray[bitIndex + k] = ((wordArray[j] >> k) & 0x01) == 1; } bitIndex += _wordSize; } } #endregion #region 모니터링 뷰에서만 사용 static public class DisplayFormat { public const string Bit16 = "16Bit"; public const string Bit32Odd = "32BitOdd"; public const string Bit32Even = "32BitEven"; public const string ASCII = "ASCII"; } public enum eDataType { Type_16Bit = 0, Type_32Bit = 1, Type_Ascii = 2, } public enum eSendResult { OK = E1.OK, Error_NotActivated = E2.NOT_READY, Error_NotConnect = E2.NOT_CONNECTED, Error_Communication = E1.NETWORK, Error_DataIsNumeric = -11111, Error_DataRange, Error_DataLength, Error_DataType, Error_Exception = -12345, } public string Conv_ReadData(eDataType eType, short lowerWord, short upperWord = 0, bool IsHex = false, bool bViewDot = false) { // LUKAS_CHECK: 여기에 try-catch 가 필요한 경우가 있을까...?? (제거 검토 목적) string strRtn = ""; try { switch (eType) { case eDataType.Type_16Bit: strRtn = string.Format("{0:X4}", lowerWord); if (!IsHex) strRtn = Utils.Hex2short(strRtn).ToString(); break; case eDataType.Type_32Bit: strRtn = string.Format("{0:X4}", upperWord) + string.Format("{0:X4}", lowerWord); if (!IsHex) strRtn = Utils.Hex2Int(strRtn).ToString(); break; case eDataType.Type_Ascii: // 워드를 char 문자로 // 1 워드는 최대 2개의 문자로 구성됨 char[] charTemp = new char[2]; string[] strData = new string[2]; // 워드값을 16진수 문자열로 표기 // ex. A (65): "0041" string strTemp = string.Format("{0:X4}", lowerWord); // 첫 2개의 문자열에 해당하는 정수를 char 문자로 변환 // note: "00" 은 널문자 char 로 변환됨 charTemp[0] = Convert.ToChar(Utils.Hex2Int(Utils.StrLeft(strTemp, 2))); // strTemp 의 2번째부터 남은 문자열에 해당하는 정수를 char 문자로 변환 charTemp[1] = Convert.ToChar(Utils.Hex2Int(Utils.StrRight(strTemp, 2))); // bViewDot: 기호, 숫자, 알파벳 범위를 벗어난 경우, . 으로 할거냐, 빈문자열로 할거냐의 의미 if (bViewDot) { // 아스키 기호, 숫자, 알파벳 범위를 벗어나면 dot 으로 대체 // 32 = space (빈칸) // lower part if ((charTemp[0] > 126) || (charTemp[0] < 32)) strData[0] = "."; // Convert.ToChar(46); else strData[0] = charTemp[0].ToString(); // upper part if ((charTemp[1] > 126) || (charTemp[1] < 32)) strData[1] = "."; // Convert.ToChar(46); else strData[1] = charTemp[1].ToString(); } else { if ((charTemp[0] > 126) || (charTemp[0] < 32)) strData[0] = ""; // Convert.ToChar(46); else strData[0] = charTemp[0].ToString(); if ((charTemp[1] > 126) || (charTemp[1] < 32)) strData[1] = ""; // Convert.ToChar(46); else strData[1] = charTemp[1].ToString(); } strRtn = strData[1] + strData[0]; break; } return strRtn; } catch (Exception ex) { Log.E($"Exception : {ex.Message}"); return ""; } } #endregion #region PLC 통신 메서드 (내부용) private (int err, bool data) ReadBit(IOBlock ioblock, int addrOffset) { if (!InRange(ioblock, addrOffset)) { return (E2.INVALID_ARGUMENT, false); } return (E1.OK, ioblock.Data[addrOffset]); } private (int error, short data) ReadWord(IOBlock ioblock, int addrOffset) { if (!InRange(ioblock, addrOffset)) { return (E2.INVALID_ARGUMENT, 0); } return (E1.OK, ioblock.Data[addrOffset]); } private (int err, short[] array) ReadWordBlock(IOBlock ioblock, int addrOffset, int wordSize) { if (!InRange(ioblock, addrOffset, wordSize)) { return (E2.INVALID_ARGUMENT, null); } // 1 워드 = 2 byte // 1 short 는 2 byte var wordArray = new short[wordSize]; Array.Copy(ioblock.Data, addrOffset, wordArray, 0, wordSize); return (E1.OK, wordArray); } private int WriteBit(IOBlock ioblock, int addrOffset, bool onOff) { if (!InRange(ioblock, addrOffset)) { return E2.INVALID_ARGUMENT; } lock (_lockBitInOut) { // 뮤텍스가 필요함 ioblock.Data[addrOffset] = onOff; } return E1.OK; } private int WriteWord(IOBlock ioblock, int addrOffset, short shData) { if (!InRange(ioblock, addrOffset)) { return E2.INVALID_ARGUMENT; } lock (_lockWordInOut) { ioblock.Data[addrOffset] = shData; } return E1.OK; } private int WriteWordBlock(IOBlock ioBlock, int addrOffset, int wdSize, short[] shArray) { if (shArray == null || shArray.Length == 0) { return E1.ARGUMENT; } if (!InRange(ioBlock, addrOffset, wdSize)) { return E2.INVALID_ARGUMENT; } lock (_lockWordInOut) { Array.Copy(shArray, 0, ioBlock.Data, addrOffset, wdSize); } return E1.OK; } private (int err, bool[] array) ReadBitBlock(IOBlock ioblock, int addrOffset, int bitSize) { if (!InRange(ioblock, addrOffset, bitSize)) { return (E2.INVALID_ARGUMENT, null); } var bitArray = new bool[bitSize]; Array.Copy(ioblock.Data, addrOffset, bitArray, 0, bitSize); return (E1.OK, bitArray); } private int WriteBitBlock(IOBlock ioBlock, int addrOffset, int bitSize, bool[] bArray) { if (bArray == null || bArray.Length == 0) { return E1.ARGUMENT; } if (!InRange(ioBlock, addrOffset, bitSize)) { return E2.INVALID_ARGUMENT; } lock (_lockBitInOut) { Array.Copy(bArray, 0, ioBlock.Data, addrOffset, bitSize); } return E1.OK; } #endregion #region 물리적 네트워킹 관련 (PLC 제조사 API 사용) private int ReadBitInputFromPLC() { PLCUpdatorInfo.UpdatePointer = OnReadBI; if (!IsConnected) { return E2.NOT_CONNECTED; } if (BitInputWordSize <= 0) { // No data return E1.OK; } if (IsSimul == false) { // Real mode short[] readDeviceBlock = new short[BitInputWordSize]; short readSize = (short)BitInputWordSize; // 기본값: 실패를 가정 var _res = false; if (NetworkType == ePLCNetType.MelsecNet) { readSize = (short)(BitInputWordSize * 2); // Using byte size _res = PLCNet.ReceiveFromPLC((int)ePLCDevice.B, (short)BitInputBlock.BaseAddress, ref readSize, ref readDeviceBlock[0]); } else if (NetworkType == ePLCNetType.MXComponent) { //string devLabel = $"B{BitInputBlock.BaseAddress:X}"; // Using word size _res = PLCNet.ReceiveFromPLC(in BitInputBlock.DevAddr, readSize, ref readDeviceBlock); } if (_res != true) { // 통신 실패하면 바로 통신 연결 플래그 해제 IsConnected = false; ++PLCUpdatorInfo.NumberOfRBFail; Log.E($"fail to receive Bit data from PLC: address: B{BitInputBlock.BaseAddress:X4}, size: {readSize}"); return E1.NETWORK; } // PLC로부터 읽어온 B 메모리 전체 블록을 버퍼에 업데이트 // 여기에 뮤텍스를 걸어야 하는데, // Read 부분이므로, 여기가 유일한 업데이트 코드임 // 시뮬에서 업데이트를 하긴 하나, 시뮬은 API 통신이 없다. // => 뮤텍스 불필요 WordArrayToBitArray(readDeviceBlock, BitInputBlock.Data); } // Simul mode 인 경우는... // 시뮬레이션의 경우, PLC 가 없으므로, 읽어올 게 없다. // LUKAS_CHECK : 시뮬 한정 가상 PLC 메모리 버퍼를 만들어야 할까?? return E1.OK; } private int ReadMBitInputFromPLC() { PLCUpdatorInfo.UpdatePointer = 3; if (!IsConnected) { return E2.NOT_CONNECTED; } if (MBitInputWordSize <= 0) { // No data return E1.OK; } if (IsSimul == false) { // Real mode short[] readDeviceBlock = new short[MBitInputWordSize]; short readSize = (short)MBitInputWordSize; // 기본값: 실패를 가정 var _res = false; if (NetworkType == ePLCNetType.MelsecNet) { readSize = (short)(MBitInputWordSize * 2); // Using byte size _res = PLCNet.ReceiveFromPLC((int)ePLCDevice.M, (short)MBitInputBlock.BaseAddress, ref readSize, ref readDeviceBlock[0]); } else if (NetworkType == ePLCNetType.MXComponent) { //string devLabel = $"B{BitInputBlock.BaseAddress:X}"; // Using word size _res = PLCNet.ReceiveFromPLC(in MBitInputBlock.DevAddr, readSize, ref readDeviceBlock); } if (_res != true) { // 통신 실패하면 바로 통신 연결 플래그 해제 IsConnected = false; ++PLCUpdatorInfo.NumberOfRBFail; Log.E($"fail to receive Bit data from PLC: address: M{MBitInputBlock.BaseAddress:X4}, size: {readSize}"); return E1.NETWORK; } // PLC로부터 읽어온 B 메모리 전체 블록을 버퍼에 업데이트 // 여기에 뮤텍스를 걸어야 하는데, // Read 부분이므로, 여기가 유일한 업데이트 코드임 // 시뮬에서 업데이트를 하긴 하나, 시뮬은 API 통신이 없다. // => 뮤텍스 불필요 WordArrayToBitArray(readDeviceBlock, MBitInputBlock.Data); } // Simul mode 인 경우는... // 시뮬레이션의 경우, PLC 가 없으므로, 읽어올 게 없다. // LUKAS_CHECK : 시뮬 한정 가상 PLC 메모리 버퍼를 만들어야 할까?? return E1.OK; } private int ReadWordInputFromPLC() { PLCUpdatorInfo.UpdatePointer = OnReadWI; if (!IsConnected) { return E2.NOT_CONNECTED; } if (WordInputWordSize <= 0) { // No data return E1.OK; } if (IsSimul == false) { // Real mode short[] readDeviceBlock = new short[WordInputWordSize]; short readSize = (short)WordInputWordSize; var _res = false; if (NetworkType == ePLCNetType.MelsecNet) { readSize = (short)(WordInputWordSize * 2); // Using byte size _res = PLCNet.ReceiveFromPLC((int)ePLCDevice.W, (short)WordInputBlock.BaseAddress, ref readSize, ref readDeviceBlock[0]); } else if (NetworkType == ePLCNetType.MXComponent) { //string devLabel = $"W{WordInputBlock.BaseAddress:X}"; _res = PLCNet.ReceiveFromPLC(in WordInputBlock.DevAddr, readSize, ref readDeviceBlock); } if (_res != true) { IsConnected = false; ++PLCUpdatorInfo.NumberOfRWFail; Log.E($"fail to receive Word data from PLC: address: W{WordInputBlock.BaseAddress:X4}, size: {readSize}"); return E1.NETWORK; } // 여기가 유일한 업데이트임 : 뮤텍스 불필요 readDeviceBlock.CopyTo(WordInputBlock.Data, 0); } return E1.OK; } private int ReadDWordInputFromPLC() { PLCUpdatorInfo.UpdatePointer = 1; if (!IsConnected) { return E2.NOT_CONNECTED; } if (DWordInputWordSize <= 0) { // No data return E1.OK; } if (IsSimul == false) { // Real mode short[] readDeviceBlock = new short[DWordInputWordSize]; short readSize = (short)DWordInputWordSize; var _res = false; if (NetworkType == ePLCNetType.MelsecNet) { readSize = (short)(WordInputWordSize * 2); // Using byte size _res = PLCNet.ReceiveFromPLC((int)ePLCDevice.D, (short)DWordInputBlock.BaseAddress, ref readSize, ref readDeviceBlock[0]); } else if (NetworkType == ePLCNetType.MXComponent) { _res = PLCNet.ReceiveFromPLC(in DWordInputBlock.DevAddr, readSize, ref readDeviceBlock); } if (_res != true) { IsConnected = false; ++PLCUpdatorInfo.NumberOfRWFail; Log.E($"fail to receive Word data from PLC: address: D{DWordInputBlock.BaseAddress:X4}, size: {readSize}"); return E1.NETWORK; } // 여기가 유일한 업데이트임 : 뮤텍스 불필요 readDeviceBlock.CopyTo(DWordInputBlock.Data, 0); } return E1.OK; } private int WriteBitOutputToPLC() { PLCUpdatorInfo.UpdatePointer = OnWriteBO; if (!IsConnected) { return E2.NOT_CONNECTED; } if (BitOutputWordSize <= 0) { // No data return E1.OK; } if (IsSimul == false) { // Real 모드 short[] writeDeviceBlock = new short[BitOutputWordSize]; short writtenSize = (short)BitOutputWordSize; BitArrayToWordArray(BitOutputBlock.Data, writeDeviceBlock); var _res = false; if (NetworkType == ePLCNetType.MelsecNet) { writtenSize = (short)(BitOutputWordSize * 2); // Using byte size _res = PLCNet.SendToPLC((int)ePLCDevice.B, (short)BitOutputBlock.BaseAddress, ref writtenSize, ref writeDeviceBlock[0]); } else if (NetworkType == ePLCNetType.MXComponent) { //string devLabel = $"B{BitOutputBlock.BaseAddress:X}"; // Using word size _res = PLCNet.SendToPLC(in BitOutputBlock.DevAddr, writtenSize, ref writeDeviceBlock); } if (_res != true) { IsConnected = false; ++PLCUpdatorInfo.NumberOfWBFail; Log.E($"Fail to Write Bit data to PLC: address: B{BitOutputBlock.BaseAddress:X4}, size: {writtenSize}"); return E1.NETWORK; } } return E1.OK; } private int WriteMBitOutputToPLC() { PLCUpdatorInfo.UpdatePointer = 7; if (!IsConnected) { return E2.NOT_CONNECTED; } if (MBitOutputWordSize <= 0) { // No data return E1.OK; } if (IsSimul == false) { // Real 모드 short[] writeDeviceBlock = new short[MBitOutputWordSize]; short writtenSize = (short)MBitOutputWordSize; BitArrayToWordArray(MBitOutputBlock.Data, writeDeviceBlock); var _res = false; if (NetworkType == ePLCNetType.MelsecNet) { writtenSize = (short)(MBitOutputWordSize * 2); // Using byte size _res = PLCNet.SendToPLC((int)ePLCDevice.M, (short)MBitOutputBlock.BaseAddress, ref writtenSize, ref writeDeviceBlock[0]); } else if (NetworkType == ePLCNetType.MXComponent) { _res = PLCNet.SendToPLC(in MBitOutputBlock.DevAddr, writtenSize, ref writeDeviceBlock); } if (_res != true) { IsConnected = false; ++PLCUpdatorInfo.NumberOfWBFail; Log.E($"Fail to Write Bit data to PLC: address: M{MBitOutputBlock.BaseAddress:X4}, size: {writtenSize}"); return E1.NETWORK; } } return E1.OK; } private int WriteWordOutputToPLC() { PLCUpdatorInfo.UpdatePointer = OnWriteWO; if (!IsConnected) { return E2.NOT_CONNECTED; } if (WordOutputWordSize <= 0) { // No data return E1.OK; } if (IsSimul == false) { // LUKAS_CHECK : Bit 는 데이터 타입이 달랐지만, // 어차피 전체 batch 기준인데, Word 장치의 경우, 굳이 지역 배열을 만들어서 복사할 필요가 있을까?? // 나중에 나중에 테스트해보자~~~~~!!! short[] writeDeviceBlock = new short[WordOutputWordSize]; short writtenSize = (short)WordOutputWordSize; Array.Copy(WordOutputBlock.Data, 0, writeDeviceBlock, 0, WordOutputWordSize); var _res = false; if (NetworkType == ePLCNetType.MelsecNet) { writtenSize = (short)(WordOutputWordSize * 2); // Using byte size _res = PLCNet.SendToPLC((int)ePLCDevice.W, (short)WordOutputBlock.BaseAddress, ref writtenSize, ref writeDeviceBlock[0]); } else if (NetworkType == ePLCNetType.MXComponent) { //string devLabel = $"W{WordOutputBlock.BaseAddress:X}"; _res = PLCNet.SendToPLC(in WordOutputBlock.DevAddr, writtenSize, ref writeDeviceBlock); } if (_res != true) { IsConnected = false; ++PLCUpdatorInfo.NumberOfWWFail; Log.E($"fail to write Word data to PLC: address: W{WordOutputBlock.BaseAddress:X4}, size: {writtenSize}"); return E1.NETWORK; } } return E1.OK; } private int WriteDWordOutputToPLC() { PLCUpdatorInfo.UpdatePointer = 5; if (!IsConnected) { return E2.NOT_CONNECTED; } if (DWordOutputWordSize <= 0) { // No data return E1.OK; } if (IsSimul == false) { // LUKAS_CHECK : Bit 는 데이터 타입이 달랐지만, // 어차피 전체 batch 기준인데, Word 장치의 경우, 굳이 지역 배열을 만들어서 복사할 필요가 있을까?? // 나중에 나중에 테스트해보자~~~~~!!! short[] writeDeviceBlock = new short[DWordOutputWordSize]; short writtenSize = (short)DWordOutputWordSize; Array.Copy(DWordOutputBlock.Data, 0, writeDeviceBlock, 0, DWordOutputWordSize); var _res = false; if (NetworkType == ePLCNetType.MelsecNet) { writtenSize = (short)(DWordOutputWordSize * 2); // Using byte size _res = PLCNet.SendToPLC((int)ePLCDevice.D, (short)DWordOutputBlock.BaseAddress, ref writtenSize, ref writeDeviceBlock[0]); } else if (NetworkType == ePLCNetType.MXComponent) { //string devLabel = $"W{WordOutputBlock.BaseAddress:X}"; _res = PLCNet.SendToPLC(in DWordOutputBlock.DevAddr, writtenSize, ref writeDeviceBlock); } if (_res != true) { IsConnected = false; ++PLCUpdatorInfo.NumberOfWWFail; Log.E($"fail to write Word data to PLC: address: D{DWordOutputBlock.BaseAddress:X4}, size: {writtenSize}"); return E1.NETWORK; } } return E1.OK; } private int ReadBitOutputFromPLC() // 최초 동기화: PLC Bit 출력 => PC Bit 출력 { if (!IsConnected) { return E2.NOT_CONNECTED; } if (BitOutputWordSize <= 0) { // No data return E1.OK; } if (IsSimul == false) { // Real mode short[] readDeviceBlock = new short[BitOutputWordSize]; short readSize = (short)BitOutputWordSize; var _res = false; if (NetworkType == ePLCNetType.MelsecNet) { readSize = (short)(BitOutputWordSize * 2); // Using byte size _res = PLCNet.ReceiveFromPLC((int)ePLCDevice.B, (short)BitOutputBlock.BaseAddress, ref readSize, ref readDeviceBlock[0]); } else if (NetworkType == ePLCNetType.MXComponent) { //string devLabel = $"B{BitOutputBlock.BaseAddress:X}"; _res = PLCNet.ReceiveFromPLC(in BitOutputBlock.DevAddr, readSize, ref readDeviceBlock); } if (_res != true) { // 통신 실패하면 바로 통신 연결 플래그 해제 IsConnected = false; ++PLCUpdatorInfo.NumberOfRBFail; Log.E($"fail to receive Bit Output data from PLC: address: B{BitOutputBlock.BaseAddress:X4}, size: {readSize}"); return E1.NETWORK; } lock (_lockBitInOut) { WordArrayToBitArray(readDeviceBlock, BitOutputBlock.Data); } } // 시뮬은 읽을게 없음.. return E1.OK; } private int ReadMBitOutputFromPLC() // 최초 동기화: PLC Bit 출력 => PC Bit 출력 { if (!IsConnected) { return E2.NOT_CONNECTED; } if (MBitOutputWordSize <= 0) { // No data return E1.OK; } if (IsSimul == false) { // Real mode short[] readDeviceBlock = new short[MBitOutputWordSize]; short readSize = (short)MBitOutputWordSize; var _res = false; if (NetworkType == ePLCNetType.MelsecNet) { readSize = (short)(MBitOutputWordSize * 2); // Using byte size _res = PLCNet.ReceiveFromPLC((int)ePLCDevice.M, (short)MBitOutputBlock.BaseAddress, ref readSize, ref readDeviceBlock[0]); } else if (NetworkType == ePLCNetType.MXComponent) { //string devLabel = $"B{BitOutputBlock.BaseAddress:X}"; _res = PLCNet.ReceiveFromPLC(in MBitOutputBlock.DevAddr, readSize, ref readDeviceBlock); } if (_res != true) { // 통신 실패하면 바로 통신 연결 플래그 해제 IsConnected = false; ++PLCUpdatorInfo.NumberOfRBFail; Log.E($"fail to receive Bit Output data from PLC: address: M{MBitOutputBlock.BaseAddress:X4}, size: {readSize}"); return E1.NETWORK; } lock (_lockBitInOut) { WordArrayToBitArray(readDeviceBlock, MBitOutputBlock.Data); } } // 시뮬은 읽을게 없음.. return E1.OK; } private int ReadWordOutputFromPLC() // 최초 동기화: PLC Word 출력 => PC Word 출력 { if (!IsConnected) { return E2.NOT_CONNECTED; } if (WordOutputWordSize <= 0) { // No data return E1.OK; } if (IsSimul == false) { // Real mode short[] readDeviceBlock = new short[WordOutputWordSize]; short readSize = (short)WordOutputWordSize; var _res = false; if (NetworkType == ePLCNetType.MelsecNet) { readSize = (short)(WordOutputWordSize * 2); // Using byte size _res = PLCNet.ReceiveFromPLC((int)ePLCDevice.W, (short)WordOutputBlock.BaseAddress, ref readSize, ref readDeviceBlock[0]); } else if (NetworkType == ePLCNetType.MXComponent) { //string devLabel = $"W{WordOutputBlock.BaseAddress:X}"; _res = PLCNet.ReceiveFromPLC(in WordOutputBlock.DevAddr, readSize, ref readDeviceBlock); } if (_res != true) { IsConnected = false; ++PLCUpdatorInfo.NumberOfRWFail; Log.E($"fail to receive Word Output data from PLC: address: W{WordOutputBlock.BaseAddress:X4}, size: {readSize}"); return E1.NETWORK; } lock (_lockWordInOut) { Array.Copy(readDeviceBlock, WordOutputBlock.Data, WordOutputWordSize); } } // 시뮬모드는 nothing return E1.OK; } private int ReadDWordOutputFromPLC() // 최초 동기화: PLC Word 출력 => PC Word 출력 { if (!IsConnected) { return E2.NOT_CONNECTED; } if (DWordOutputWordSize <= 0) { // No data return E1.OK; } if (IsSimul == false) { // Real mode short[] readDeviceBlock = new short[DWordOutputWordSize]; short readSize = (short)DWordOutputWordSize; var _res = false; if (NetworkType == ePLCNetType.MelsecNet) { readSize = (short)(DWordOutputWordSize * 2); // Using byte size _res = PLCNet.ReceiveFromPLC((int)ePLCDevice.D, (short)DWordOutputBlock.BaseAddress, ref readSize, ref readDeviceBlock[0]); } else if (NetworkType == ePLCNetType.MXComponent) { //string devLabel = $"W{WordOutputBlock.BaseAddress:X}"; _res = PLCNet.ReceiveFromPLC(in DWordOutputBlock.DevAddr, readSize, ref readDeviceBlock); } if (_res != true) { IsConnected = false; ++PLCUpdatorInfo.NumberOfRWFail; Log.E($"fail to receive Word Output data from PLC: address: D{DWordOutputBlock.BaseAddress:X4}, size: {readSize}"); return E1.NETWORK; } lock (_lockWordInOut) { Array.Copy(readDeviceBlock, DWordOutputBlock.Data, DWordOutputWordSize); } } // 시뮬모드는 nothing return E1.OK; } #endregion #region PLC Data processing private void DataUpdator() { // 비동기 PLC 데이터 update if (IsSimul == false) { // Real mode // Update // Note: 비동기 스레드의 동기화 체크 필요 // 활성화, 비활성화 부분 // 통신 연결/해제 부분 // 업데이트 순서 (워드 입력 -> 비트 입력 -> 워드 출력 -> 비트 출력) // 0 : 시작 PLCUpdatorInfo.UpdatePointer = RWStart; // --- Read // Receive (Word Input buffers) from PLC ReadWordInputFromPLC(); ReadDWordInputFromPLC(); // Read WordInput 완료 PLCUpdatorInfo.UpdatePointer = AfterReadWI; // WI 완료 ++PLCUpdatorInfo.NumberOfReadWord; // Receive (Bit Input buffers) from PLC ReadBitInputFromPLC(); ReadMBitInputFromPLC(); // Read BitInput 완료 PLCUpdatorInfo.UpdatePointer = AfterReadBI; // BI 완료 ++PLCUpdatorInfo.NumberOfReadBit; // --- Send // Send (Word Output buffers) to PLC WriteWordOutputToPLC(); WriteDWordOutputToPLC(); // Write WordOutput 완료 PLCUpdatorInfo.UpdatePointer = AfterWriteWO; // WO 완료 ++PLCUpdatorInfo.NumberOfWriteWord; // Send (Bit Output buffers) to PLC WriteBitOutputToPLC(); WriteMBitOutputToPLC(); // Write BitOutput 완료 PLCUpdatorInfo.UpdatePointer = RWEnd; // BO 완료 ++PLCUpdatorInfo.NumberOfWriteBit; } else { // simul mode PLCUpdatorInfo.UpdatePointer = RWStart; PLCUpdatorInfo.UpdatePointer = AfterReadWI; // WI 완료 ++PLCUpdatorInfo.NumberOfReadBit; PLCUpdatorInfo.UpdatePointer = AfterReadBI; // BI 완료 ++PLCUpdatorInfo.NumberOfReadWord; PLCUpdatorInfo.UpdatePointer = AfterWriteWO; // WO 완료 ++PLCUpdatorInfo.NumberOfWriteBit; PLCUpdatorInfo.UpdatePointer = RWEnd; // BO 완료 ++PLCUpdatorInfo.NumberOfWriteWord; } ++PLCUpdatorInfo.NumberOfUpdateCycle; } #endregion } }