1160 lines
52 KiB
C#
1160 lines
52 KiB
C#
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
|
|
//---------------------------------------
|
|
|
|
/// <summary>
|
|
/// Read PLC Bit
|
|
/// ** Read 이므로, 기본값은 isInput = true 임
|
|
/// </summary>
|
|
/// <param name="addrOffset"></param>
|
|
/// <param name="isOutput"></param>
|
|
/// <returns>bit value</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read PLC Bit
|
|
/// ** 위 기본형 ReadBit와 동일한 함수를 return type만 다른 방식으로 제공
|
|
/// ** Read 이므로, 기본값은 isInput = true 임
|
|
/// </summary>
|
|
/// <param name="addrOffset"></param>
|
|
/// <param name="data"></param>
|
|
/// <param name="isInput"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read PLC Word
|
|
/// ** Read 이므로, 기본값은 isInput = true 임
|
|
/// </summary>
|
|
/// <param name="addrOffset"></param>
|
|
/// <param name="isInput"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read PLC Word
|
|
/// ** Read 이므로, 기본값은 isInput = true 임
|
|
/// ** 위 기본형 ReadWord와 동일한 함수를 return type만 다른 방식으로 제공
|
|
/// </summary>
|
|
/// <param name="addrOffset"></param>
|
|
/// <param name="isInput"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read PLC DWord
|
|
/// ** Read 이므로, 기본값은 isInput = true 임
|
|
/// </summary>
|
|
/// <param name="addrOffset"></param>
|
|
/// <param name="isInput"></param>
|
|
/// <returns></returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read PLC DWord
|
|
/// ** Read 이므로, 기본값은 isInput = true 임
|
|
/// ** 위 기본형 ReadDWord와 동일한 함수를 return type만 다른 방식으로 제공
|
|
/// </summary>
|
|
/// <param name="addrOffset"></param>
|
|
/// <param name="isInput"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read PLC DWord (+ Decimal Scale 지정)
|
|
/// ** Read 이므로, 기본값은 isInput = true 임
|
|
/// </summary>
|
|
/// <param name="addrOffset"></param>
|
|
/// <param name="scaleDecimalPt"></param>
|
|
/// <param name="isInput"></param>
|
|
/// <returns></returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read PLC Word Block (Word를 특정 size만큼 읽기)
|
|
/// ** Read 이므로, 기본값은 isInput = true 임
|
|
/// </summary>
|
|
/// <param name="addrOffset"></param>
|
|
/// <param name="scaleDecimalPt"></param>
|
|
/// <param name="isInput"></param>
|
|
/// <returns></returns>
|
|
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
|
|
//---------------------------------------
|
|
|
|
/// <summary>
|
|
/// Write PLC Bit
|
|
/// ** Write 이므로, 기본값은 isOutput = true 임
|
|
/// </summary>
|
|
/// <param name="addrOffset"></param>
|
|
/// <param name="onOff"></param>
|
|
/// <param name="isOutput"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write PLC Word
|
|
/// ** Write 이므로, 기본값은 isOutput = true 임
|
|
/// </summary>
|
|
/// <param name="addrOffset"></param>
|
|
/// <param name="shData"></param>
|
|
/// <param name="isOutput"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write PLC DWord
|
|
/// ** Write 이므로, 기본값은 isOutput = true 임
|
|
/// </summary>
|
|
/// <param name="addrOffset"></param>
|
|
/// <param name="iVal"></param>
|
|
/// <param name="isOutput"></param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write PLC Word Block (Word를 특정 size만큼 쓰기)
|
|
/// ** Write 이므로, 기본값은 isOutput = true 임
|
|
/// </summary>
|
|
/// <param name="addrOffset"></param>
|
|
/// <param name="wordSize"></param>
|
|
/// <param name="shArray"></param>
|
|
/// <param name="isOutput"></param>
|
|
/// <returns></returns>
|
|
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
|
|
}
|
|
}
|