using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.Threading; namespace arDev { public class RS232 : IDisposable { protected Boolean _isinit = false; #region "Event Args" /// /// 데이터를 수신할떄 사용함(RAW 포함) /// public class ReceiveDataEventArgs : EventArgs { private readonly byte[] _buffer = null; /// /// 바이트배열의 버퍼값 /// public byte[] Value { get { return _buffer; } } /// /// 버퍼(바이트배열)의 데이터를 문자로 반환합니다. /// public string StrValue { get { //return string.Empty; if (_buffer == null || _buffer.Length < 1) return string.Empty; else return System.Text.Encoding.Default.GetString(_buffer); } } public ReceiveDataEventArgs(byte[] buffer) { _buffer = buffer; } } public class MessageEventArgs : EventArgs { private readonly Boolean _isError = false; public Boolean IsError { get { return _isError; } } private readonly string _message = string.Empty; public string Message { get { return _message; } } public MessageEventArgs(Boolean isError, string Message) { _isError = isError; _message = Message; } } #endregion #region "Enum & Structure" /// /// 데이터수신시 해당 데이터의 끝을 결정하는 방식을 설정합니다. /// public enum eTerminal : byte { /// /// line feed /// LF = 0, /// /// carrige return /// CR, /// /// cr+lf /// CRLF, /// /// stx + ETx 구성된 프로토콜을 감지합니다. stx,etx는 임의 지정이 가능하며 기본값으로는 stx = 0x02, etx = 0x03 을 가지고 있습니다. /// ETX, /// /// 데이터의 길이를 가지고 판단합니다. /// Length, /// /// 설정없음 .. 일정시간동안 대기한 후 버퍼의 내용을 모두 데이터로 인정합니다. /// None, /// /// 내부 Receive 이벤트를 사용하지 않고 Raw 이벤트를 사용합니다. /// 이 값을 설정할 경우 내부 Receivce 이벤트 내에서 메세지 수신 이벤트가 발생하지 않습니다. /// DataParSER을 상속하여 해당 파서에서 데이터를 분리하세요. True 이면 분리성공, false 이면 완료될때까지 대기합니다. /// CustomParser } #endregion #region "Public variable" /// /// WriteDataSync 명령 사용시 최대로 기다리는 시간 /// public int syncTimeout = 5000; /// /// 오류가 발생했다면 이 변수에 그 내용이 기록됩니다. /// public string errorMessage = string.Empty; ///// ///// WriteDataSync 명령 사용시 최대로 기다리는 시간 ///// //public int syncTimeout = 5000; /// /// 이 값은 종단기호 형식이 Length 일때 사용됩니다. 버퍼의 갯수이 이 값과 일치하면 수신 이벤트를 발생합니다. /// public int MaxDataLength = 0x0d; /// /// 마지막으로 데이터는 전송한 시간 /// public DateTime lastSendTime; /// /// 최종 전송 메세지 /// public byte[] lastSendBuffer; /// /// 마지막으로 데이터를 받은 시간 /// public DateTime lastRecvTime = DateTime.Parse("1982-11-23"); /// /// 데이터 전송시 전송메세지를 발생할것인가? 171113 /// public Boolean EnableTxMessage { get; set; } /// /// terminal 이 stx 일때에만 사용하며 기본값은 0x03 /// [Description("종단기호형식이 ETX일때 사용하는 데이터의 종료문자값입니다. 바이트값이므로 0~255 사이로 입력하세요.")] [Category("설정"), DisplayName("Data End Byte")] public byte ETX { get; set; } #endregion #region "Protect & private Variable" protected Boolean CheckACK { get; set; } protected Boolean CheckNAK { get; set; } /// /// 메세지 수신시 사용하는 내부버퍼 /// protected List _buffer = new List(); /// /// 데이터의 끝을 분석하는 종단기호의 설정 /// private eTerminal _term = eTerminal.LF; /// /// WriteDataSync 명령사용시 활성화됨 /// protected Boolean _isSync = false; /// /// sync timeOUt체크시 사용합니다. /// private System.Diagnostics.Stopwatch _wat = new System.Diagnostics.Stopwatch(); /// /// Serialport Device /// protected System.IO.Ports.SerialPort _device; /// /// for autoreset events /// protected ManualResetEvent _mre; protected Boolean isDisposed = false; #endregion #region "Internal Events" void barcode_ErrorReceived(object sender, System.IO.Ports.SerialErrorReceivedEventArgs e) { if (Message != null) Message(this, new MessageEventArgs(true, e.ToString())); } void barcode_PinChanged(object sender, System.IO.Ports.SerialPinChangedEventArgs e) { if (serialPinchanged != null) serialPinchanged(this, e); //if (Message != null) Message(this, new MessageEventArgs(true, "PinChanged")); } void barcode_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e) { this.lastRecvTime = DateTime.Now; if (_isSync) return; //싱크모드일경우에는 해당 루틴에서 직접 읽는다 _isSync = false; //none일경우에는 100ms 정도 기다려준다. if (_term == eTerminal.None) { //none 일경우에는 무조건 데이터로 취급한다. System.Threading.Thread.Sleep(200); _buffer.Clear(); } try { int ReadCount = _device.BytesToRead; byte[] buffer = new byte[ReadCount]; _device.Read(buffer, 0, buffer.Length); if (ReceiveData_Raw != null) ReceiveData_Raw(this, new ReceiveDataEventArgs(buffer)); System.Text.StringBuilder LogMsg = new StringBuilder(); if (Terminal == eTerminal.CustomParser) { byte[] remainBuffer; Repeat: if (CustomParser(buffer, out remainBuffer)) { //parser ok RaiseRecvData(_buffer.ToArray()); _buffer.Clear(); if (remainBuffer != null && remainBuffer.Length > 0) { //버퍼를 변경해서 다시 전송을 해준다. buffer = new byte[remainBuffer.Length]; Array.Copy(remainBuffer, buffer, buffer.Length); goto Repeat; //남은 버퍼가 있다면 진행을 해준다. } } } else { foreach (byte bb in buffer) { switch (_term) { case eTerminal.CR: if (bb == 0x0D) { RaiseRecvData(_buffer.ToArray()); ; _buffer.Clear(); } else _buffer.Add(bb); break; case eTerminal.LF: if (bb == 0x0A) { RaiseRecvData(_buffer.ToArray()); ; _buffer.Clear(); } else _buffer.Add(bb); break; case eTerminal.CRLF: if (bb == 0x0A) { RaiseRecvData(_buffer.ToArray()); ; _buffer.Clear(); } else if (bb == 0x0D) { //0d는 그냥 넘어간다. } else _buffer.Add(bb); break; case eTerminal.Length: _buffer.Add(bb); if (_buffer.Count == MaxDataLength) { RaiseRecvData(_buffer.ToArray()); ; _buffer.Clear(); } else if (_buffer.Count > MaxDataLength) { RaiseMessage("Buffer Length Error " + _buffer.Count.ToString() + "/" + MaxDataLength.ToString(), true); _buffer.Clear(); } break; case eTerminal.ETX: //asc타입의 프로토콜에서는 STX,ETX가 고유값이다. if (bb == STX) { _buffer.Clear(); } else if (bb == ETX) { RaiseRecvData(_buffer.ToArray()); ; _buffer.Clear(); } else _buffer.Add(bb); break; case eTerminal.None: _buffer.Add(bb); break; } } //170802 if (_term == eTerminal.None) { RaiseRecvData(_buffer.ToArray()); ; _buffer.Clear(); } } } catch (Exception ex) { if (IsOpen()) { //_device.DiscardInBuffer(); //_device.DiscardOutBuffer(); } errorMessage = ex.Message; RaiseMessage(ex.Message, true); } } #endregion #region "External Events" /// /// 바코드에서 들어오는 데이터의 원본 메세지 /// public event EventHandler ReceiveData_Raw; /// /// 데이터가 들어올 경우 발생합니다 (종단기호=Termianl) 문자열이 발견된 후에 발생함 /// public event EventHandler ReceiveData; /// /// 데이터를 전송할 때 해당 이벤트가 발생합니다. /// public event EventHandler SendData; /// /// 오류 및 기타 일반 메세지 /// public event EventHandler Message; /// /// 시리얼포트의 핀 상태값이 변경될 때 발생합니다. /// public event EventHandler serialPinchanged; #endregion #region "Properties" /// /// 식별번호(임의지정가능) - 장치 생성시 입력 /// [Description("이 장치의 식별 ID(임의 지정가능)")] [Category("설정"), DisplayName("Device No")] public string Tag { get; set; } /// /// terminal 이 etx 일때에만 사용하며 기본값은 0x02 /// [Description("종단기호형식이 ETX일때 사용하는 데이터의 시작문자값입니다. 바이트값이므로 0~255 사이로 입력하세요.")] [Category("설정"), DisplayName("Data Start Byte")] public byte STX { get; set; } /// /// 내장 분석기(Parser)를 사용할 경우 최종 데이터에서 CR,LF를 제거할지 선택합니다. /// [Description("내장분석기(Parser)를 사용할 경우 최종 데이터에서 CR.LF를 제거할지 선택합니다.")] [Category("기타"), DisplayName("CRLF 제거")] public Boolean RemoveCRLFNULL { get; set; } /// /// 종단기호 형식 /// [Description("데이터의 종단기호를 설정합니다. 지정한 데이터가 올경우")] [Category("설정"), DisplayName("종단기호")] public eTerminal Terminal { get { return _term; } set { _term = value; } } [Category("설정")] public System.IO.Ports.Parity Parity { get { return _device.Parity; } set { _device.Parity = value; } } [Category("설정")] public int DataBits { get { return _device.DataBits; } set { _device.DataBits = value; } } [Category("설정")] public System.IO.Ports.StopBits StopBits { get { return _device.StopBits; } set { _device.StopBits = value; } } [Category("설정")] public System.IO.Ports.Handshake Handshake { get { return _device.Handshake; } set { _device.Handshake = value; } } #region "pin state & pin setting" /// /// Data Terminal Ready /// [Description("Data Terminal Ready 신호의 사용여부")] [Category("PIN")] public Boolean DtrEnable { get { return _device.DtrEnable; } set { _device.DtrEnable = value; } } /// /// Request To Send /// [Description("Request to Send 신호의 사용여부")] [Category("PIN")] public Boolean RtsEnable { get { return _device.RtsEnable; } set { _device.RtsEnable = value; } } /// /// Data set Ready 신호 상태 /// [Description("Data Set Ready 신호 상태")] [Category("PIN")] public Boolean PIN_DSR { get { if (!IsOpen()) return false; return _device.DsrHolding; } } /// /// Carrier Detect /// [Description("Carrier Detect 신호 상태")] [Category("PIN")] public Boolean PIN_CD { get { if (!IsOpen()) return false; return _device.CDHolding; } } /// /// Clear to Send /// [Description("Clear to Send 신호 상태")] [Category("PIN")] public Boolean PIN_CTS { get { if (!IsOpen()) return false; return _device.CtsHolding; } } /// /// Break State /// [Description("중단신호 상태")] [Category("PIN")] public Boolean PIN_BreakState { get { if (!IsOpen()) return false; return _device.BreakState; } } #endregion /// /// 포트가 열려있는지 확인 /// [Description("현재 시리얼포트가 열려있는지 확인합니다")] [Category("정보"), DisplayName("Port Open")] public virtual Boolean IsOpen() { if (_device == null) return false; return _device.IsOpen; } /// /// 초기화등이 성공했는지 확인합니다.close 되었다면 실패입니다. isinit 변수값을 적절히 수정하시기 바랍니다. /// [Description("초기화성공여부 별도의 초기화 코드가없다면 isOpen 과 동일합니다.")] [Category("정보"), DisplayName("Init OK?")] public virtual Boolean IsInit() { if (!IsOpen() || !_isinit) return false; return true; } /// /// 쓰기타임아웃 /// [Description("쓰기명령어의 최대대기시간(단위:ms)\r\n지정 시간을 초과할 경우 오류가 발생합니다.")] [Category("설정"), DisplayName("쓰기 제한시간")] public int WriteTimeout { get { return _device.WriteTimeout; } set { _device.WriteTimeout = value; } } /// /// 읽기타임아웃 /// [Description("읽기명령어의 최대대기시간(단위:ms)\r\n지정 시간을 초과할 경우 오류가 발생합니다.")] [Category("설정"), DisplayName("읽기 제한시간")] public int ReadTimeout { get { return _device.ReadTimeout; } set { _device.ReadTimeout = value; } } /// /// 포트이름 /// [Description("시리얼 포트 이름")] [Category("설정"), DisplayName("Port Name")] public string PortName { get { return _device.PortName; } set { if (string.IsNullOrEmpty(value) == false) _device.PortName = value; } } /// /// RS232 Baud Rate /// [Description("시리얼 포트 전송 속도")] [Category("설정"), DisplayName("Baud Rate")] public int BaudRate { get { return _device.BaudRate; } set { _device.BaudRate = value; } } #endregion #region "Method" /// /// 쓰기버퍼비우기 /// public void ClearWriteBuffer() { if (_device.IsOpen) _device.DiscardOutBuffer(); } /// /// 읽기버퍼비우기 /// public void ClearReadBuffer() { if (_device.IsOpen) _device.DiscardInBuffer(); } /// /// 장치의 초기화작업을 수행합니다.(이 값은 기본값으로 true가 무조건 설정됩니다) 오버라이드하여 각 상황에 맞게 처리하세요. /// protected virtual void Init() { if (!IsOpen()) _isinit = false; else _isinit = true; } protected virtual Boolean CustomParser(byte[] buf, out byte[] remainBuffer) { remainBuffer = new byte[] { }; return true; } #region "Raise Message Events (임의로 메세지를 발생시킵니다)" /// /// 보낸메세지 이벤트를 발생 /// /// String Data public void RaiseSendData(string data) { RaiseSendData(System.Text.Encoding.Default.GetBytes(data)); } /// /// 보낸메세지 이벤트를 발생 합니다. /// /// Byte Array public void RaiseSendData(byte[] data) { try { if (SendData != null) SendData(this, new ReceiveDataEventArgs(data)); } catch (Exception ex) { RaiseMessage("RaiseSendData:" + ex.Message, true); } } /// /// 지정한 데이터로 바코드가 수신된것처럼 발생시킵니다. /// /// public void RaiseRecvData(byte[] b) { byte[] Data; if (RemoveCRLFNULL) //제거해야하는경우에만 처리 170822 Data = RemoveCRLF(b); else Data = b; try { if (ReceiveData != null) ReceiveData(this, new ReceiveDataEventArgs(Data)); } catch (Exception ex) { RaiseMessage("RaiseDataMessage:" + ex.Message, true); } } /// /// 지정한 데이터로 바코드가 수신된것처럼 발생시킵니다. /// /// public void RaiseRecvData(string data) { RaiseRecvData(System.Text.Encoding.Default.GetBytes(data)); } /// /// 메세지이벤트를 발생합니다. 오류메세지일 경우 2번째 파라미터를 true 로 입력하세요. /// /// 메세지 /// 오류라면 True로 설정하세요. 기본값=False public void RaiseMessage(string message, Boolean isError = false) { if (isError) errorMessage = message; //170920 if (Message != null) Message(this, new MessageEventArgs(isError, message)); } #endregion /// /// 포트열기(실패시 False)를 반환 /// public virtual Boolean Open(Boolean runInit = true) { try { _device.Open(); if (_device.IsOpen) { Init(); } else { _isinit = false; } return _isinit; } catch (Exception ex) { errorMessage = ex.Message; RaiseMessage(ex.Message, true); return false; } } public virtual Boolean Open(string portname, int baud, Boolean runInit = true) { try { this.PortName = portname; this.BaudRate = baud; _device.Open(); if (_device.IsOpen) { Init(); } else { _isinit = false; } return _isinit; } catch (Exception ex) { errorMessage = ex.Message; RaiseMessage(ex.Message, true); return false; } } /// /// 포트닫기 /// public virtual void Close() { if (_device != null && _device.IsOpen) { _isinit = false; _device.DiscardInBuffer(); _device.DiscardOutBuffer(); //외부에서 닫기를 하면 진행된다? System.Threading.Tasks.Task.Run(new Action(() => { _device.Close(); })); } } /// /// 메세지내용중 Cr,LF 를 제거합니다. /// protected byte[] RemoveCRLF(byte[] src) { List bcdbuf = new List(); foreach (byte by in src) { if (by == 0x00 || by == 0x0d || by == 0x0a || by == 0x02 || by == 0x03) continue; bcdbuf.Add(by); } return bcdbuf.ToArray(); } #endregion #region "Method Write Data" /// /// 포트에 쓰기(barcode_DataReceived 이벤트로 메세지수신) /// public virtual Boolean WriteData(string data) { byte[] buf = System.Text.Encoding.Default.GetBytes(data); return WriteData(buf); } public virtual Boolean Write(string data) { return WriteData(data); } public virtual Boolean Write(byte[] buf) { return WriteData(buf); } /// /// 포트에 쓰기 반환될 때까지 기다림(SyncTimeOut 값까지 기다림) /// public virtual byte[] WriteDataSync(string data) { byte[] buf = System.Text.Encoding.Default.GetBytes(data); return WriteDataSync(buf); } /// /// _buffer를 클리어하고 입력된 데이터를 버퍼에 추가합니다. /// /// public void setRecvBuffer(byte[] buf) { this._buffer.Clear(); this._buffer.AddRange(buf); } public int WriteError = 0; public string WriteErrorMessage = string.Empty; /// /// 포트에 쓰기 반환될 때까지 기다림(SyncTimeOut 값까지 기다림) /// public virtual byte[] WriteDataSync(byte[] data, Boolean useReset = true) { errorMessage = string.Empty; _isSync = true; byte[] recvbuf = null; // Boolean bRet = false; //171214 if (!IsOpen()) { errorMessage = "Port Closed"; return null; } //171205 : 타임아웃시간추가 if (useReset) { if (!_mre.WaitOne(syncTimeout)) { errorMessage = string.Format("WriteDataSync:MRE:WaitOne:TimeOut 3000ms"); RaiseMessage(errorMessage, true); return null; } _mre.Reset(); } //save last command lastSendTime = DateTime.Now; if (lastSendBuffer == null) lastSendBuffer = new byte[data.Length]; //171113 else Array.Resize(ref lastSendBuffer, data.Length); Array.Copy(data, lastSendBuffer, data.Length); Boolean sendOK = false; try { _device.DiscardInBuffer(); _device.DiscardOutBuffer(); _buffer.Clear(); //171205 _device.Write(data, 0, data.Length); WriteError = 0; WriteErrorMessage = string.Empty; sendOK = true; } catch (Exception ex) { WriteError += 1; WriteErrorMessage = ex.Message; } if (sendOK) { try { //171113 if (EnableTxMessage && SendData != null) SendData(this, new ReceiveDataEventArgs(data)); _wat.Restart(); Boolean bTimeOut = false; _buffer.Clear(); Boolean bDone = false; while (!bDone) { if (_wat.ElapsedMilliseconds > WriteTimeout) { errorMessage = "(Sync)WriteTimeOut"; bTimeOut = true; break; } else { int RecvCnt = _device.BytesToRead; if (RecvCnt > 0) { byte[] rbuf = new byte[RecvCnt]; _device.Read(rbuf, 0, rbuf.Length); if (_term == eTerminal.CustomParser) { byte[] remainBuffer; Repeat: if (CustomParser(rbuf, out remainBuffer)) { var newMem = new byte[_buffer.Count]; _buffer.CopyTo(newMem); RaiseRecvData(newMem); bDone = true; if (remainBuffer != null && remainBuffer.Length > 0) { rbuf = new byte[remainBuffer.Length]; Buffer.BlockCopy(remainBuffer, 0, rbuf, 0, rbuf.Length); goto Repeat; } } } else { foreach (byte b in rbuf) { if (CheckACK && b == 0x06) //ack { _buffer.Add(b); bDone = true; break; } else if (CheckNAK && b == 0x15) //nak { _buffer.Add(b); bDone = true; break; } else { switch (_term) { case eTerminal.CR: if (b == 0x0D) { bDone = true; break; } else _buffer.Add(b); break; case eTerminal.LF: if (b == 0x0A) { bDone = true; break; } else _buffer.Add(b); break; case eTerminal.CRLF: if (b == 0x0A) { bDone = true; break; } else if (b == 0x0d) { //pass } else { _buffer.Add(b); } break; case eTerminal.Length: _buffer.Add(b); if (_buffer.Count == MaxDataLength) { bDone = true; break; } else if (_buffer.Count > MaxDataLength) { RaiseMessage("Buffer Length Error " + _buffer.Count.ToString() + "/" + MaxDataLength.ToString(), true); _buffer.Clear(); } break; case eTerminal.ETX: if (b == STX) { _buffer.Clear(); } else if (b == ETX) { bDone = true; break; } else _buffer.Add(b); break; } } } } } } } _wat.Stop(); if (!bTimeOut) { recvbuf = new byte[_buffer.Count]; Buffer.BlockCopy(_buffer.ToArray(), 0, recvbuf, 0, recvbuf.Length); //recvbuf = _buffer.ToArray(); // bRet = true; } } catch (Exception ex) { errorMessage = ex.Message; //bRet = false; } finally { if (useReset) _mre.Set();//release } } //syncmode off _isSync = false; return recvbuf; } /// /// 포트에 쓰기(barcode_DataReceived 이벤트로 메세지수신) /// public virtual Boolean WriteData(byte[] data) { Boolean bRet = false; //171205 : 타임아웃시간추가 if (!_mre.WaitOne(3000)) { errorMessage = string.Format("WriteData:MRE:WaitOne:TimeOut 3000ms"); RaiseMessage(errorMessage, true); return false; } _mre.Reset(); //Array.Resize(ref data, data.Length + 2); try { lastSendTime = DateTime.Now; if (lastSendBuffer == null) lastSendBuffer = new byte[data.Length]; //171113 else Array.Resize(ref lastSendBuffer, data.Length); Array.Copy(data, lastSendBuffer, data.Length); _device.Write(data, 0, data.Length); //171113 if (EnableTxMessage && SendData != null) SendData(this, new ReceiveDataEventArgs(data)); bRet = true; WriteError = 0; WriteErrorMessage = string.Empty; } catch (Exception ex) { // this.isinit = false; RaiseMessage(ex.Message, true);// if (ReceivceData != null) ReceivceData(this, true, ex.Message); bRet = false; WriteError += 1; //연속쓰기오류횟수 WriteErrorMessage = ex.Message; } finally { _mre.Set(); } return bRet; } #endregion /// /// 지정한ID를 가진 장치를 생성합니다. /// /// public RS232(string tag_ = "") { _mre = new ManualResetEvent(true); this.Tag = tag_; this._device = new System.IO.Ports.SerialPort(); _device.DataReceived += barcode_DataReceived; _device.ErrorReceived += barcode_ErrorReceived; _device.PinChanged += barcode_PinChanged; _device.ReadTimeout = 2000; _device.WriteTimeout = 2000; _device.BaudRate = 9600; _term = eTerminal.CRLF; STX = 0x02; ETX = 0x03; RemoveCRLFNULL = false; EnableTxMessage = false; } ~RS232() { Dispose(); } /// /// close를 호출합니다. /// public virtual void Dispose() { if (!isDisposed) //180219 { isDisposed = true; _isinit = false; _device.DataReceived -= barcode_DataReceived; _device.ErrorReceived -= barcode_ErrorReceived; _device.PinChanged -= barcode_PinChanged; Close(); } } } }