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 완료
///
/// 각형은 순서를 바꾸어보고 싶음.. 비트 써진 후, 워드를 읽어서 워드 값을 보장 받고 싶었음..
/// 쓸 때는 Word를 먼저 써주고, Bit를 쓰게끔..
///
const int RBStart = 0; // RB 시작
const int OnReadBI = 1; // Read Bit Input 중
const int AfterReadBI = 2; // Read Bit Input 완료
const int OnReadWI = 3; // Read Word Input 중
const int AfterReadWI = 4; // Read Word 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 관련 메모리 버퍼 및 초기화
internal struct IOBlockSegment
{
public int PlcBaseAddress; // PLC 실제 시작 주소 (예: 70000, 70900)
public int DataOffset; // 이 segment가 IOBlock.Data에서 시작하는 인덱스
public int Length; // 이 segment의 길이 (워드 수)
}
// 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 List Segments = new List();
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)
{
wordBlock.Segments.Clear();
if (config.Count != 0) {
var startAddress = config[0].address;
wordBlock.BaseAddress = startAddress;
wordBlock.Count = config.Count;
wordBlock.Data = new short[wordBlock.Count];
// 연속 구간(segment) 계산
int segStartAddr = config[0].address;
int segStartIndex = 0;
int prevAddr = segStartAddr;
for (int i = 1; i < config.Count; i++) {
int curAddr = config[i].address;
if (curAddr != prevAddr + 1) {
wordBlock.Segments.Add(new IOBlockSegment {
PlcBaseAddress = segStartAddr,
DataOffset = segStartIndex,
Length = i - segStartIndex
});
segStartAddr = curAddr;
segStartIndex = i;
}
prevAddr = curAddr;
}
wordBlock.Segments.Add(new IOBlockSegment {
PlcBaseAddress = segStartAddr,
DataOffset = segStartIndex,
Length = config.Count - segStartIndex
});
} else {
wordBlock.Count = 0;
wordBlock.Data = Array.Empty();
}
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 = OnReadBI;
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 = OnReadWI;
if (!IsConnected) {
return E2.NOT_CONNECTED;
}
if (DWordInputWordSize <= 0) { // No data
return E1.OK;
}
if (IsSimul == false) { // Real mode
foreach (var seg in DWordInputBlock.Segments) {
short[] readDeviceBlock = new short[seg.Length];
short readSize = (short)seg.Length;
bool _res = false;
if (NetworkType == ePLCNetType.MelsecNet) {
readSize = (short)(seg.Length * 2); // Using byte size
_res = PLCNet.ReceiveFromPLC((int)ePLCDevice.D, (short)seg.PlcBaseAddress, ref readSize, ref readDeviceBlock[0]);
} else if (NetworkType == ePLCNetType.MXComponent) {
string devLabel = $"D{seg.PlcBaseAddress}";
_res = PLCNet.ReceiveFromPLC(in devLabel, seg.Length, ref readDeviceBlock);
}
if (_res != true) {
IsConnected = false;
++PLCUpdatorInfo.NumberOfRWFail;
Log.E($"fail to receive Word data from PLC: address: D{seg.PlcBaseAddress:D}, size: {readSize}");
return E1.NETWORK;
}
// 읽어온 값은 IOBlock.Data 의 해당 offset에 복사
Array.Copy(readDeviceBlock, 0, DWordInputBlock.Data, seg.DataOffset, seg.Length);
}
}
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 = OnWriteBO;
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 = OnWriteWO;
if (!IsConnected) {
return E2.NOT_CONNECTED;
}
if (DWordOutputWordSize <= 0) { // No data
return E1.OK;
}
if (IsSimul == false) {
// LUKAS_CHECK : Bit 는 데이터 타입이 달랐지만,
// 어차피 전체 batch 기준인데, Word 장치의 경우, 굳이 지역 배열을 만들어서 복사할 필요가 있을까??
// 나중에 나중에 테스트해보자~~~~~!!!
foreach (var seg in DWordOutputBlock.Segments) {
short[] writeDeviceBlock = new short[seg.Length];
Array.Copy(DWordOutputBlock.Data, seg.DataOffset, writeDeviceBlock, 0, seg.Length);
short writtenSize = (short)seg.Length;
bool _res = false;
if (NetworkType == ePLCNetType.MelsecNet) {
writtenSize = (short)(seg.Length * 2); // Using byte size
_res = PLCNet.SendToPLC((int)ePLCDevice.D, (short)seg.PlcBaseAddress, ref writtenSize, ref writeDeviceBlock[0]);
} else if (NetworkType == ePLCNetType.MXComponent) {
string devLabel = $"D{seg.PlcBaseAddress}";
_res = PLCNet.SendToPLC(in devLabel, writtenSize, ref writeDeviceBlock);
}
if (_res != true) {
IsConnected = false;
++PLCUpdatorInfo.NumberOfWWFail;
Log.E($"fail to write Word data to PLC: address: D{seg.PlcBaseAddress:D}, 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
foreach (var seg in DWordOutputBlock.Segments) {
short[] readDeviceBlock = new short[seg.Length];
short readSize = (short)seg.Length;
bool _res = false;
if (NetworkType == ePLCNetType.MelsecNet) {
readSize = (short)(seg.Length * 2); // Using byte size
_res = PLCNet.ReceiveFromPLC((int)ePLCDevice.D, (short)seg.PlcBaseAddress, ref readSize, ref readDeviceBlock[0]);
} else if (NetworkType == ePLCNetType.MXComponent) {
string devLabel = $"D{seg.PlcBaseAddress}";
_res = PLCNet.ReceiveFromPLC(in devLabel, seg.Length, ref readDeviceBlock);
}
if (_res != true) {
IsConnected = false;
++PLCUpdatorInfo.NumberOfRWFail;
Log.E($"fail to receive Word Output data from PLC: address: D{seg.PlcBaseAddress:D}, size: {readSize}");
return E1.NETWORK;
}
lock (_lockWordInOut) {
Array.Copy(readDeviceBlock, 0, DWordOutputBlock.Data, seg.DataOffset, seg.Length);
}
}
}
// 시뮬모드는 nothing
return E1.OK;
}
#endregion
#region PLC Data processing
private void DataUpdator()
{
// 비동기 PLC 데이터 update
if (IsSimul == false) { // Real mode
// Update
// Note: 비동기 스레드의 동기화 체크 필요
// 활성화, 비활성화 부분
// 통신 연결/해제 부분
// 업데이트 순서 (워드 입력 -> 비트 입력 -> 워드 출력 -> 비트 출력)
// 0 : 시작
PLCUpdatorInfo.UpdatePointer = RBStart;
// --- 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;
//// 최신 Word 값을 더 갖고 싶어서 수정하였음.
//// 그러나, Word 값은 조금 딜레이를 발생하면서 써지는 것처럼 보임...
// Receive (Bit Input buffers) from PLC
// Receive (Word Input buffers) from PLC
ReadBitInputFromPLC();
ReadMBitInputFromPLC();
// Read BitInput 완료
PLCUpdatorInfo.UpdatePointer = AfterReadBI; // BI 완료
++PLCUpdatorInfo.NumberOfReadBit;
ReadWordInputFromPLC();
ReadDWordInputFromPLC();
// Read WordInput 완료
PLCUpdatorInfo.UpdatePointer = AfterReadWI; // WI 완료
++PLCUpdatorInfo.NumberOfReadWord;
// --- 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 = RBStart;
//PLCUpdatorInfo.UpdatePointer = AfterReadWI; // WI 완료
//++PLCUpdatorInfo.NumberOfReadBit;
//PLCUpdatorInfo.UpdatePointer = AfterReadBI; // BI 완료
//++PLCUpdatorInfo.NumberOfReadWord;
PLCUpdatorInfo.UpdatePointer = AfterReadBI; // BI 완료
++PLCUpdatorInfo.NumberOfReadWord;
PLCUpdatorInfo.UpdatePointer = AfterReadWI; // WI 완료
++PLCUpdatorInfo.NumberOfReadBit;
PLCUpdatorInfo.UpdatePointer = AfterWriteWO; // WO 완료
++PLCUpdatorInfo.NumberOfWriteBit;
PLCUpdatorInfo.UpdatePointer = RWEnd; // BO 완료
++PLCUpdatorInfo.NumberOfWriteWord;
}
++PLCUpdatorInfo.NumberOfUpdateCycle;
}
#endregion
}
}