2026-01-26 14:51:28 +09:00

908 lines
38 KiB
C#

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<T>
{
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<bool> BitInputBlock; // Input: PC <-- PLC
private IOBlock<bool> BitOutputBlock; // Output: PC --> PLC
private IOBlock<bool> MBitInputBlock;
private IOBlock<bool> MBitOutputBlock;
private IOBlock<short> WordInputBlock;
private IOBlock<short> WordOutputBlock;
private IOBlock<short> DWordInputBlock;
private IOBlock<short> DWordOutputBlock;
private void InitBitBlock(IList<(int address, AddrCommentList descript)> config, IOBlock<bool> 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<short> 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<T>(IOBlock<T> 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;
}
}
/// <summary>
/// 입력: wordArray 를 bitArray 에 저장한다.
/// </summary>
/// <param name="wordArray"></param>
/// <param name="bitArray"></param>
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<bool> 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<short> 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<short> 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<bool> 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<short> 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<short> 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<bool> 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<bool> 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
}
}