arMaskterK 프로젝트를 vms2016_cs 레포에서 현 레포로 이동
This commit is contained in:
686
Sub/arMasterK/Project/MasterK.cs
Normal file
686
Sub/arMasterK/Project/MasterK.cs
Normal file
@@ -0,0 +1,686 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Design;
|
||||
using System.Reflection;
|
||||
using System.Runtime;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace arDev
|
||||
{
|
||||
public partial class MasterK : IDisposable
|
||||
{
|
||||
#region "Variable"
|
||||
|
||||
public bool bRunMonitor; //쓰레드작동시그널
|
||||
public Boolean bAIMonitor=false; //배경쓰레드에서 아날로그값을 모니터링 합니다.\
|
||||
public Boolean bDIOMonitor=true; //배경쓰레드에서 dIO를 모니터링 할것인가?
|
||||
public string errorMessage = string.Empty;
|
||||
|
||||
System.Diagnostics.Stopwatch aiMonWatch = new System.Diagnostics.Stopwatch();
|
||||
public long LoopDelay;
|
||||
|
||||
protected byte wSTX = 0x05;
|
||||
protected byte wETX = 0x04;
|
||||
protected byte rSTX = 0x06;
|
||||
protected byte rETX = 0x03;
|
||||
protected int interchar_delay;
|
||||
protected Boolean bSyncRecvOK = false;// 메세지를 수신할 경우 설정됩니다.
|
||||
protected Boolean _isInit = false;// 보드가 초기화가 되었는지 확인합니다.
|
||||
public Boolean disposed = false;// Dipose 되었는가?
|
||||
protected byte _CommandCode = 0x00;// recv command code
|
||||
|
||||
protected Boolean _bDlc = false;
|
||||
protected int _dlc = 0;
|
||||
protected Boolean _bStx = false; //stx를 찾앗는지?
|
||||
protected Boolean _bEtx = false; //etx를 찾앗는지?
|
||||
|
||||
protected Boolean[] _valueI; //입력접점
|
||||
protected Boolean[] _valueO; //출력접점
|
||||
protected Boolean[] OldvalueI;
|
||||
protected Boolean[] OldvalueO;
|
||||
|
||||
protected Thread thMonitor; //배경쓰레드(상태체크 및 이벤트 발생)
|
||||
protected ManualResetEvent _mre;
|
||||
protected string _mreowner = string.Empty;
|
||||
protected System.Diagnostics.Stopwatch wat = new System.Diagnostics.Stopwatch();
|
||||
protected System.IO.Ports.SerialPort _device;
|
||||
|
||||
public int DOCount { get; protected set; }
|
||||
public int DICount { get; protected set; }
|
||||
public double ThreadSpeed = 0;
|
||||
|
||||
public sRecvMessage LastRecvMessage;
|
||||
public sSendMessage lastSendMessage;
|
||||
public DateTime lastSendTime;
|
||||
public byte[] lastSendBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// device index
|
||||
/// </summary>
|
||||
public byte IDX = 0;
|
||||
|
||||
#endregion
|
||||
|
||||
public MasterK()
|
||||
{
|
||||
//serial port
|
||||
_device = new System.IO.Ports.SerialPort();
|
||||
_device.BaudRate = 9600;
|
||||
_device.DtrEnable = false;
|
||||
_device.WriteTimeout = 2000;
|
||||
_device.ReadTimeout = 2000;
|
||||
_device.DiscardNull = true;
|
||||
|
||||
//autoreset events
|
||||
_mre = new System.Threading.ManualResetEvent(true);
|
||||
bRunMonitor = false;
|
||||
|
||||
LoopDelay = 150;
|
||||
|
||||
//모든ls산전은 논리적으로 64개가 지정되어있다.
|
||||
this.DICount = 64;
|
||||
this.DOCount = 64;
|
||||
|
||||
//변수초기화
|
||||
this._valueI = new bool[DICount];
|
||||
this._valueO = new bool[DOCount];
|
||||
this.OldvalueI = new bool[_valueI.Length];
|
||||
this.OldvalueO = new bool[_valueO.Length];
|
||||
|
||||
//초기값설정(인풋)
|
||||
for (int i = 0; i < _valueI.Length; i++)
|
||||
{
|
||||
this._valueI[i] = false;
|
||||
this.OldvalueI[i] = false;
|
||||
}
|
||||
|
||||
//초기값설정(아웃풋)
|
||||
for (int i = 0; i < _valueO.Length; i++)
|
||||
{
|
||||
this._valueO[i] = false;
|
||||
this.OldvalueO[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
~MasterK()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (_device.IsOpen)
|
||||
{
|
||||
if (_mre.WaitOne(3000))
|
||||
{
|
||||
_mre.Reset();
|
||||
_mreowner = "Close";
|
||||
_device.Close();
|
||||
_mre.Set();
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseMessage(true, "close error timeout");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 개체를 해제합니다.
|
||||
/// </summary>
|
||||
[Description("")]
|
||||
public virtual void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
// Protected implementation of Dispose pattern.
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
// Dispose managed resources.
|
||||
}
|
||||
|
||||
StopMonitor();
|
||||
|
||||
// Call the appropriate methods to clean up
|
||||
// unmanaged resources here.
|
||||
// If disposing is false,
|
||||
// only the following code is executed.
|
||||
|
||||
// Note disposing has been done.
|
||||
disposed = true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public string PortName
|
||||
{
|
||||
get
|
||||
{
|
||||
return _device.PortName;
|
||||
}
|
||||
set
|
||||
{
|
||||
_device.PortName = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Boolean Init(string port,int baud = 19200)
|
||||
{
|
||||
Close();
|
||||
|
||||
errorMessage = string.Empty;
|
||||
|
||||
_device.PortName = port;
|
||||
_device.BaudRate = baud;
|
||||
|
||||
try
|
||||
{
|
||||
_device.Open();
|
||||
_device.DiscardInBuffer();
|
||||
_device.DiscardOutBuffer();
|
||||
|
||||
_isInit = true;
|
||||
sw_ch = new System.Diagnostics.Stopwatch();
|
||||
interchar_delay = GetIntercharDelay();
|
||||
interframe_delay = GetInterframeDelay();
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_isInit = false;
|
||||
errorMessage = ex.Message;
|
||||
RaiseMessage(true, ex.Message);
|
||||
}
|
||||
return _isInit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#region "Properties"
|
||||
|
||||
/// <summary>
|
||||
/// 입력핀 상태값을 확인합니다.
|
||||
/// </summary>
|
||||
/// <param name="arridx">0부터 시작하는 인덱스값</param>
|
||||
/// <returns></returns>
|
||||
[Description("")]
|
||||
public Boolean INPUT(int arridx)
|
||||
{
|
||||
if (arridx < 0 || arridx > DICount - 1)
|
||||
{
|
||||
if (IOErrorMessage != null)
|
||||
IOErrorMessage(this, new MessageEventArgs(true, "INPUT Command : ArrayIndex Error Value=" + arridx.ToString()));
|
||||
return false;
|
||||
}
|
||||
return this._valueI[arridx];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 입력핀 상태를 모두 반환합니다.
|
||||
/// </summary>
|
||||
[Description("")]
|
||||
public Boolean[] getInputArray
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Boolean[])this._valueI.Clone();
|
||||
}
|
||||
}
|
||||
|
||||
public byte getInputValueToByte(int startindex)
|
||||
{
|
||||
System.Collections.BitArray ba = new System.Collections.BitArray(this._valueI);
|
||||
byte[] value = new byte[8];
|
||||
ba.CopyTo(value, startindex);
|
||||
return value[0];
|
||||
}
|
||||
public Int64 getInputValue
|
||||
{
|
||||
get
|
||||
{
|
||||
System.Collections.BitArray ba = new System.Collections.BitArray(this._valueI);
|
||||
byte[] value = new byte[8];
|
||||
ba.CopyTo(value, 0);
|
||||
return BitConverter.ToInt64(value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 출력핀 상태를 모두 반환합니다.
|
||||
/// </summary>
|
||||
[Description("")]
|
||||
public Boolean[] getOutputArray
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Boolean[])this._valueO.Clone();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 출력핀 상태 값을 확인합니다.
|
||||
/// </summary>
|
||||
/// <param name="arridx">0부터 시작하는 인덱스값</param>
|
||||
/// <returns></returns>
|
||||
[Description("")]
|
||||
public Boolean OUTPUT(int arridx)
|
||||
{
|
||||
// get { return (Boolean[])this._valueO.Clone(); }
|
||||
if (arridx < 0 || arridx > DOCount - 1)
|
||||
{
|
||||
if (IOErrorMessage != null)
|
||||
IOErrorMessage(this, new MessageEventArgs(true, "OUTPUT Command : ArrayIndex Error Value=" + arridx.ToString()));
|
||||
return false;
|
||||
}
|
||||
return this._valueO[arridx];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// read digital input port state
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Description("")]
|
||||
public string GetInputState()
|
||||
{
|
||||
System.Text.StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < OldvalueI.Length; i++)
|
||||
sb.Append(OldvalueI[i] ? "1" : "0");
|
||||
return sb.ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// read digital output port state
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Description("")]
|
||||
public string GetOutputState()
|
||||
{
|
||||
System.Text.StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < OldvalueO.Length; i++)
|
||||
sb.Append(OldvalueO[i] ? "1" : "0");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Method"
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 오류메세지 반환
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Description("")]
|
||||
public string GetErrorMessage()
|
||||
{
|
||||
return this.errorMessage;
|
||||
}
|
||||
|
||||
|
||||
#region "raise Message"
|
||||
|
||||
/// <summary>
|
||||
/// 일반메세지륿 라생시킵니다.
|
||||
/// </summary>
|
||||
/// <param name="isError"></param>
|
||||
/// <param name="message"></param>
|
||||
[Description("")]
|
||||
public void RaiseMessage(Boolean isError, string message)
|
||||
{
|
||||
if (isError) errorMessage = message; //170920
|
||||
if (Message != null) Message(this, new MessageEventArgs(isError, message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정한 데이터로 바코드가 수신된것처럼 발생시킵니다.(Parser를 사용함)
|
||||
/// </summary>
|
||||
/// <param name="b"></param>
|
||||
[Description("")]
|
||||
public void RaiseDataMessage(eMsgType dir, byte[] Data)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (dir == eMsgType.Rx) Parse_RecvBuffer(Data);
|
||||
if (DataMessage != null) DataMessage(this, new DataEventArgs(dir,Data));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = ex.Message;
|
||||
RaiseMessage(true, "RaiseDataMessage:" + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Input / Output 핀 상태를 지속적으로 읽는 쓰레드작업을 시작합니다.
|
||||
/// </summary>
|
||||
[Description("")]
|
||||
public void RunMonitor(int loopDelay_ = 150)
|
||||
{
|
||||
this.LoopDelay = loopDelay_;
|
||||
if(!this.bRunMonitor)
|
||||
{
|
||||
this.bRunMonitor = true;
|
||||
this.thMonitor = new Thread(new ThreadStart(this.bwMonitor));
|
||||
this.thMonitor.IsBackground = true;
|
||||
this.thMonitor.Start();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// INPUT/OUPUT 핀 상태를 모니터링하는 쓰레드를 종료시킵니다.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Description("")]
|
||||
public bool StopMonitor()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.bRunMonitor = false;
|
||||
RaiseMessage(false, "Stop Monitor");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RaiseMessage(true, ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정한 출력포트의 값을 반전 시킵니다.
|
||||
/// </summary>
|
||||
/// <param name="arrIndex"></param>
|
||||
/// <returns></returns>
|
||||
[Description("")]
|
||||
public Boolean SetToggle(int arrIndex)
|
||||
{
|
||||
if (!IsInit) return false;
|
||||
if (arrIndex < 0 || (arrIndex + 1) > DOCount)
|
||||
throw new Exception(string.Format("IDX Range Errror({0}~{1})", 0, DOCount-1));
|
||||
|
||||
Boolean currentValue = OUTPUT(arrIndex);
|
||||
return SetOutput(arrIndex, !currentValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 출력을 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="arrIndex"></param>
|
||||
/// <param name="Value"></param>
|
||||
/// <param name="Duration"></param>
|
||||
/// <returns></returns>
|
||||
[Description("")]
|
||||
public Boolean SetOutput(int arrIndex, Boolean Value, int Duration = 0)
|
||||
{
|
||||
errorMessage = string.Empty;
|
||||
if (!IsInit)
|
||||
{
|
||||
//RaiseMessage(true, "SetOutput:No Init");
|
||||
return false;
|
||||
}
|
||||
|
||||
//RaiseMessage(false, "Set Output Index=" + arrIndex.ToString() + ",Value=" + Value.ToString());
|
||||
|
||||
if (arrIndex < 0 || (arrIndex + 1) > DOCount)
|
||||
{
|
||||
RaiseMessage(true, "SetOutput:ArrIndex Error = " + arrIndex.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_mre.WaitOne(1000))
|
||||
{
|
||||
RaiseMessage(true, "SetOutput Wait on timeout");
|
||||
return false;
|
||||
}
|
||||
_mre.Reset();
|
||||
|
||||
if (Duration < 0) Duration = 0;
|
||||
int Port = 0x40 + arrIndex; //출력포트는 64번부터 시작한다.
|
||||
string cmd = "00WSS0107%PX00" + Port.ToString("X2") + (Value ? "01":"00");
|
||||
|
||||
//해당라인에 그값을 쓴다.
|
||||
Boolean bRet = true;
|
||||
|
||||
try
|
||||
{
|
||||
var buffer = WriteSync(cmd);
|
||||
string strBuffer = System.Text.Encoding.Default.GetString(buffer);
|
||||
if (buffer == null)
|
||||
{
|
||||
RaiseMessage(true, "SetOutput:No Response");
|
||||
bRet= false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(buffer[0] == 0x06)
|
||||
{
|
||||
if (Duration != 0)
|
||||
{
|
||||
var task = new Task(() =>
|
||||
{
|
||||
Thread.Sleep(Duration);
|
||||
|
||||
string cmd2 = "00WSS0107%PX00" + Port.ToString("X2") + (Value ? "00" : "01");
|
||||
var buffer2 = WriteSync(cmd2);
|
||||
if (buffer2 == null)
|
||||
{
|
||||
RaiseMessage(true, "SetOutput:Duration toggle Error:No Reponse");
|
||||
}
|
||||
});
|
||||
task.Start(); //비동기로 실행한다.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseMessage(true, "SetOutput:Nak response");
|
||||
bRet = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RaiseMessage(true, "SetOutput:"+ex.Message);
|
||||
bRet = false;
|
||||
}
|
||||
_mre.Set();
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 이벤트를 강제 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="Direction"></param>
|
||||
/// <param name="ArrIDX"></param>
|
||||
/// <param name="newvalue"></param>
|
||||
[Description("")]
|
||||
public void RaiseEvent(eIOPINDIR Direction, int ArrIDX, Boolean newvalue)
|
||||
{
|
||||
if (IOValueChanged != null)
|
||||
{
|
||||
//mre.WaitOne();
|
||||
//mre.Reset();
|
||||
|
||||
//171226
|
||||
if (Direction == eIOPINDIR.INPUT)
|
||||
{
|
||||
_valueI[ArrIDX] = newvalue;
|
||||
OldvalueI[ArrIDX] = newvalue;
|
||||
}
|
||||
else
|
||||
{
|
||||
_valueO[ArrIDX] = newvalue;
|
||||
OldvalueO[ArrIDX] = newvalue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (IOValueChanged != null)
|
||||
IOValueChanged(this, new IOValueEventArgs(Direction, ArrIDX, !newvalue, newvalue));
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (IOErrorMessage != null)
|
||||
this.IOErrorMessage(this, new MessageEventArgs(true , string.Format( "{0},{1}:{2}:{3}",ex.Message,Direction,ArrIDX,newvalue)));
|
||||
}
|
||||
//mre.Set();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public virtual void UserMonitorCode() { }
|
||||
|
||||
private byte loopIndex = 0;
|
||||
/// <summary>
|
||||
/// I/O상태를 모니터링 하여 내부 변수에 값을 저장합니다.
|
||||
/// </summary>
|
||||
[Description("")]
|
||||
protected void bwMonitor()
|
||||
{
|
||||
DateTime lastaitime = DateTime.Now;
|
||||
DateTime lastmottime = DateTime.Now;
|
||||
System.Diagnostics.Stopwatch wat = new System.Diagnostics.Stopwatch();
|
||||
while (this.bRunMonitor)
|
||||
{
|
||||
wat.Restart();
|
||||
//170905
|
||||
if (!IsInit)
|
||||
{
|
||||
aiMonWatch.Stop();
|
||||
System.Threading.Thread.Sleep(1000);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!aiMonWatch.IsRunning) aiMonWatch.Reset();
|
||||
}
|
||||
|
||||
//read dio status
|
||||
if (loopIndex == 0)
|
||||
{
|
||||
//RaiseMessage(false,"Run IOMonitor");
|
||||
if (bDIOMonitor)
|
||||
Read_RelayPort();
|
||||
loopIndex += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
//RaiseMessage(false, "Run AIMonitor");
|
||||
UserMonitorCode();
|
||||
loopIndex = 0;
|
||||
}
|
||||
|
||||
wat.Stop();
|
||||
ThreadSpeed = wat.ElapsedMilliseconds;
|
||||
if(wat.ElapsedMilliseconds < LoopDelay)
|
||||
{
|
||||
Thread.Sleep((int)(LoopDelay- wat.ElapsedMilliseconds));
|
||||
}
|
||||
}
|
||||
wat.Stop();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Send Method"
|
||||
|
||||
//public Boolean Read_AIPort()
|
||||
//{
|
||||
// if (!_mre.WaitOne(1000))
|
||||
// {
|
||||
// RaiseMessage(true, "Read_AIPort Wait on timeout");
|
||||
// return false;
|
||||
// }
|
||||
// //RaiseMessage(false, "Read_AIPort");
|
||||
// _mre.Reset();
|
||||
// System.Threading.Thread.Sleep(25);
|
||||
// _mre.Set();
|
||||
// return true;
|
||||
//}
|
||||
|
||||
public Boolean isValid
|
||||
{
|
||||
get
|
||||
{
|
||||
var ts = DateTime.Now - lastrecvTime;
|
||||
if (ts.TotalSeconds > 5) return false;
|
||||
else return true;
|
||||
}
|
||||
}
|
||||
private DateTime lastrecvTime = DateTime.Now;
|
||||
|
||||
public Boolean Read_RelayPort()
|
||||
{
|
||||
if(!_mre.WaitOne(1000))
|
||||
{
|
||||
RaiseMessage(true, "Read_RelayPort Wait on timeout");
|
||||
return false;
|
||||
}
|
||||
|
||||
// RaiseMessage(false, "Read_RelayPort");
|
||||
|
||||
_mre.Reset();
|
||||
var packet = MakePacket(0, 'R', "SB","%PW000", 8); //"00RSB06%PW00008"
|
||||
var buffer = WriteSync(packet);
|
||||
if(buffer != null)
|
||||
{
|
||||
var frame = Parse_RecvBuffer(buffer);
|
||||
if(!frame.isError)
|
||||
{
|
||||
this.Run_RelayPortData(ref frame);
|
||||
lastrecvTime = DateTime.Now;
|
||||
}
|
||||
|
||||
LastRecvMessage = frame;
|
||||
}
|
||||
_mre.Set();
|
||||
return buffer != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="Station">0~9</param>
|
||||
/// <param name="Command">R,W</param>
|
||||
/// <param name="CmdType">SB,SW ??</param>
|
||||
/// <param name="Device">%PW000</param>
|
||||
/// <param name="NumberofData">1</param>
|
||||
/// <param name="data">HexString</param>
|
||||
/// <returns></returns>
|
||||
public string MakePacket(byte Station,char Command,string CmdType,string Device,byte NumberofData,string data = "")
|
||||
{
|
||||
string buffer = Station.ToString("X2").PadLeft(2, '0') +
|
||||
Command +
|
||||
CmdType +
|
||||
Device.Length.ToString("X2").PadLeft(2, '0') +
|
||||
Device +
|
||||
NumberofData.ToString("X2").PadLeft(2, '0');
|
||||
if (data != "") buffer += data;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user