#include "stdafx.h" #include "debug.h" #include "tstmodemDlg.h" #include "serialport.h" #include "serialparams.h" #include "stringutils.h" #include #include "boost_lite.h" SerialPort::SerialPort() : m_rcv(32768), m_snd(1024) { } SerialPort::~SerialPort() { close(); } bool SerialPort::open() { DWORD t0= GetTickCount(); m_hPort= CreateFile(m_device, GENERIC_READ | GENERIC_WRITE, 0,NULL, OPEN_EXISTING, 0, NULL); if (m_hPort==NULL || m_hPort==INVALID_HANDLE_VALUE) { error("opening port %ls\n", m_device); m_hPort= NULL; return false; } COMMPROP comprop; if (!GetCommProperties (m_hPort, &comprop)) error("GetCommProperties"); else { debug("comprops: pl=%04x pv=%04x svcm=%08lx %08lx maxtx=%x maxrx=%x maxbd=%d typ=%08lx caps=%08lx parms=%08lx bd=%08lx dta=%04x pty=%04x curtx=%x currx=%x pv=%08lx %08lx\n", comprop.wPacketLength, comprop.wPacketVersion, comprop.dwServiceMask, comprop.dwReserved1, comprop.dwMaxTxQueue, comprop.dwMaxRxQueue, comprop.dwMaxBaud, comprop.dwProvSubType, comprop.dwProvCapabilities, comprop.dwSettableParams, comprop.dwSettableBaud, comprop.wSettableData, comprop.wSettableStopParity, comprop.dwCurrentTxQueue, comprop.dwCurrentRxQueue, comprop.dwProvSpec1, comprop.dwProvSpec2); } UpdateCommParams(); // omitting EV_RXFLAG if (!SetCommMask(m_hPort, EV_BREAK|EV_CTS|EV_DSR|EV_ERR|EV_RING|EV_RLSD|EV_RXCHAR|EV_TXEMPTY)) { error("SetCommMask"); return false; } m_hThread= CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MainThreadProc, this, 0, &m_nThreadId); if (m_hThread==INVALID_HANDLE_VALUE || m_hThread==NULL) { error("ERROR creating NHthread\n"); m_hThread= NULL; } DWORD t1= GetTickCount(); debug("port %ls open in %d msec\n", m_device, t1-t0); return true; } void SerialPort::close() { if (m_hThread) { TerminateThread(m_hThread, 0); if (!CloseHandle(m_hThread)) error("CloseHandle(hThread)"); m_hThread= 0; } if (m_hPort) { if (!CloseHandle(m_hPort)) error("CloseHandle(hPort)"); m_hPort= 0; } } DWORD SerialPort::MainThreadProc(SerialPort *port) { return port->PortThreadProc(); } CString EventString(DWORD event) { CString str; if (event&EV_BREAK) str+= L" BREAK"; if (event&EV_CTS) str+= L" CTS"; if (event&EV_DSR) str+= L" DSR"; if (event&EV_ERR) str+= L" ERR"; if (event&EV_RING) str+= L" RING"; if (event&EV_RLSD) str+= L" RLSD"; if (event&EV_RXCHAR) str+= L" RX"; if (event&EV_RXFLAG) str+= L" RF"; if (event&EV_TXEMPTY) str+= L" TX"; if (event & ~(EV_BREAK|EV_CTS|EV_DSR|EV_ERR|EV_RING|EV_RLSD|EV_RXCHAR|EV_TXEMPTY|EV_RXFLAG) ) str+= L" ??"; return str; } CString ModemStatusString(DWORD modem) { CString str; if (modem&MS_CTS_ON) str+= L" CTS"; if (modem&MS_DSR_ON) str+= L" DSR"; if (modem&MS_RING_ON) str+= L" RING"; if (modem&MS_RLSD_ON) str+= L" RLSD"; if (modem& ~(MS_CTS_ON|MS_DSR_ON|MS_RING_ON|MS_RLSD_ON) ) str+= L" ??"; return str; } DWORD SerialPort::PortThreadProc() { DWORD event; while (WaitCommEvent(m_hPort, &event, NULL)) { DWORD modem=0; if (!GetCommModemStatus(m_hPort, &modem)) error("GetCommModemStatus"); DCB dcb; dcb.DCBlength= sizeof(DCB); if (!GetCommState(m_hPort, &dcb)) error("GetCommState"); debug("event %ls, modem=%ls\n", EventString(event).GetBuffer(0), ModemStatusString(modem).GetBuffer(0)); if (event&EV_BREAK) handleStartBreakEvent(modem, dcb); else handleEndBreakEvent(modem, dcb); if (event&EV_CTS) handleCTSEvent(modem, dcb); if (event&EV_DSR) handleDSREvent(modem, dcb); if (event&EV_ERR) handleERREvent(modem, dcb); if (event&EV_RING) handleRINGEvent(modem, dcb); if (event&EV_RLSD) handleRLSDEvent(modem, dcb); if (event&EV_RXCHAR) handleRXCharEvent(modem, dcb); //if (event&EV_RXFLAG) handleRXFlagEvent(modem, dcb); if (event&EV_TXEMPTY) handleTXEmptyEvent(modem, dcb); } error("WaitCommEvent"); //m_dlg->PostMessage(WM_QUIT, 0, 0); return 0; } void SerialPort::handleStartBreakEvent(DWORD modem, const DCB& dcb) { m_dlg->CommEventBreak(true); } // mmmm, 'end of break' event seems to be missing in this API. // assuming any other event to mean 'end of break' void SerialPort::handleEndBreakEvent(DWORD modem, const DCB& dcb) { m_dlg->CommEventBreak(false); } void SerialPort::handleCTSEvent(DWORD modem, const DCB& dcb) { m_dlg->CommEventCTS((modem&MS_CTS_ON)!=0); } void SerialPort::handleDSREvent(DWORD modem, const DCB& dcb) { m_dlg->CommEventDSR((modem&MS_DSR_ON)!=0); } void SerialPort::handleERREvent(DWORD modem, const DCB& dcb) { m_dlg->CommEventERR(1); COMSTAT commStatus; DWORD dwErrors = 0; if (!ClearCommError(m_hPort, &dwErrors, &commStatus)) { error("ClearCommError"); return; } StringList errors; if (dwErrors&CE_BREAK) errors.push_back("BREAK"); if (dwErrors&CE_FRAME) errors.push_back("FRAME"); if (dwErrors&CE_IOE) errors.push_back("IOE"); if (dwErrors&CE_MODE) errors.push_back("MODE"); if (dwErrors&CE_OVERRUN) errors.push_back("OVERRUN"); if (dwErrors&CE_RXOVER) errors.push_back("RXOVER"); if (dwErrors&CE_RXPARITY) errors.push_back("RXPARITY"); if (dwErrors&CE_TXFULL) errors.push_back("TXFULL"); if (dwErrors&~(CE_BREAK|CE_FRAME|CE_IOE|CE_MODE|CE_OVERRUN|CE_RXOVER|CE_RXPARITY|CE_TXFULL)) errors.push_back(stringformat("unknown err: %08lx", dwErrors&~(CE_BREAK|CE_FRAME|CE_IOE|CE_MODE|CE_OVERRUN|CE_RXOVER|CE_RXPARITY|CE_TXFULL))); if (dwErrors) debug("LINEERROR: %hs\n", JoinStringList(errors, ",").c_str()); m_dlg->CommEventERR(0); // todo: handle comstat. } void SerialPort::handleRINGEvent(DWORD modem, const DCB& dcb) { // no ringoff event? m_dlg->CommEventRing(true); m_dlg->CommEventRing(false); } void SerialPort::handleRLSDEvent(DWORD modem, const DCB& dcb) { m_dlg->CommEventRLSD((modem&MS_RLSD_ON)!=0); } void SerialPort::handleRXCharEvent(DWORD modem, const DCB& dcb) { m_dlg->CommEventReceive(); DWORD nTotalRead=0; DWORD nRead= 0; bool overflow= false; do { boost::mutex::scoped_lock lock(m_rcvmtx); if (m_rcv.size==m_rcv.maxbuf) { m_rcv.overflows++; overflow= true; break; } if (m_rcv.head==m_rcv.maxbuf) m_rcv.head= 0; if (!ReadFile(m_hPort, m_rcv.buf+m_rcv.head, m_rcv.tail<=m_rcv.head?m_rcv.maxbuf-m_rcv.head : m_rcv.tail-m_rcv.head, &nRead, NULL)) { error("ReadFile"); return; } m_rcv.head+=nRead; m_rcv.size+=nRead; nTotalRead+=nRead; } while(nRead>0); if (overflow) m_dlg->CommEventInputOverFlow(); //debug("received %d bytes\n", nTotalRead); m_rcvcond.notify_one(); m_dlg->PostMessage(WM_RECEIVEDATA, 0, 0); } int SerialPort::ReceiveData(CString& str) { str.Empty(); boost::mutex::scoped_lock lock(m_rcvmtx); DWORD t0= GetTickCount(); int buflen= m_rcv.size; TCHAR *buf= str.GetBuffer(buflen+1); int i=0; while (!m_rcv.size && GetTickCount()-t0<200) m_rcvcond.wait(lock, 200); DWORD nReceived=0; while (m_rcv.size) { if (i>=buflen) { buflen+=m_rcv.size; str.ReleaseBuffer(i); buf= str.GetBuffer(buflen+1); } if (m_rcv.tail==m_rcv.maxbuf) m_rcv.tail= 0; int wanted= m_rcv.maxbuf-m_rcv.tail; if (wanted > m_rcv.size) wanted = m_rcv.size; memcpy(buf+i, m_rcv.buf+m_rcv.tail, wanted); nReceived+=wanted; m_rcv.size-=wanted; m_rcv.tail+=wanted; i+=wanted; } str.ReleaseBuffer(i); //debug("%8d : received %4d bytes: %ls\n", t0, nReceived, str.GetBuffer(0)); return nReceived; } /* void SerialPort::handleRXFlagEvent(DWORD modem, const DCB& dcb) { } */ void SerialPort::handleTXEmptyEvent(DWORD modem, const DCB& dcb) { m_dlg->CommEventTransmit(); DWORD nTotalWritten=0; DWORD nWritten=0; boost::mutex::scoped_lock lock(m_sndmtx); if (m_snd.size==0) return; do { if (m_snd.tail==m_snd.maxbuf) m_snd.tail= 0; if (!WriteFile(m_hPort, m_snd.buf+m_snd.tail, m_snd.head<=m_snd.tail? m_snd.maxbuf-m_snd.tail : m_snd.head-m_snd.tail, &nWritten, NULL)) { error("WriteFile"); return; } m_snd.tail+=nWritten; m_snd.size-=nWritten; nTotalWritten+=nWritten; } while (nWritten>0 && m_snd.size>0); if (nTotalWritten) { //debug("wrote %d bytes\n", nTotalWritten); } } bool SerialPort::SendData(CString& str) { if (m_hPort==0) return false; TCHAR *buf= str.GetBuffer(0); int buflen= str.GetLength(); DWORD t0= GetTickCount(); //debug("%8d : sending %4d bytes: %ls\n", t0, buflen, str.GetBuffer(0)); bool overflow= false; while (buflen) { boost::mutex::scoped_lock lock(m_sndmtx); if (m_snd.size==m_snd.maxbuf) { m_snd.overflows++; overflow= true; } if (m_snd.head==m_snd.maxbuf) m_snd.head= 0; m_snd.buf[m_snd.head++]= (BYTE)*buf++; buflen--; m_snd.size++; } if (overflow) { m_dlg->CommEventOutputOverFlow(); return false; } // these status things are not really nescesary..... DWORD modem=0; if (!GetCommModemStatus(m_hPort, &modem)) { error("GetCommModemStatus"); return false; } DCB dcb; dcb.DCBlength= sizeof(DCB); if (!GetCommState(m_hPort, &dcb)) { error("GetCommState"); return false; } handleTXEmptyEvent(modem, dcb); return true; } void SerialPort::SetView(CTstmodemDlg& dlg) { m_dlg= &dlg; } bool SerialPort::SetBreak(bool state) { if (m_hPort==0) return false; if (!EscapeCommFunction(m_hPort, state?SETBREAK:CLRBREAK)) { error("EscapeCommFunction(%hsBREAK)", state?"SET":"CLR"); return false; } //debug("set break to %d\n", state?1:0); return true; } bool SerialPort::SetDTR(bool state) { if (m_hPort==0) return false; if (!EscapeCommFunction(m_hPort, state?SETDTR:CLRDTR)) { error("EscapeCommFunction(%hsDTR)", state?"SET":"CLR"); return false; } //debug("set dtr to %d\n", state?1:0); return true; } bool SerialPort::SetRTS(bool state) { if (m_hPort==0) return false; if (!EscapeCommFunction(m_hPort, state?SETRTS:CLRRTS)) { error("EscapeCommFunction(%hsRTS)", state?"SET":"CLR"); return false; } //debug("set rts to %d\n", state?1:0); return true; } bool SerialPort::SetIRMode(bool state) { if (m_hPort==0) return false; if (!EscapeCommFunction(m_hPort, state?SETIR:CLRIR)) { error("EscapeCommFunction(%hsIR)", state?"SET":"CLR"); return false; } //debug("set ir to %d\n", state?1:0); return true; } bool SerialPort::SetRtsCtsFlowControl(bool state) { if (m_hPort==0) return false; DCB dcb; dcb.DCBlength= sizeof(DCB); if (!GetCommState(m_hPort, &dcb)) { error("GetCommState"); return false; } dcb.fRtsControl= state ? RTS_CONTROL_HANDSHAKE:RTS_CONTROL_ENABLE; dcb.fOutxCtsFlow= state; if (!SetCommState(m_hPort, &dcb)) { error("SetCommState"); return false; } return true; } bool SerialPort::SendIoctl() { if (m_hPort==0) return false; BYTE comdevcmd[2]= {0x84, 0x00}; if (!DeviceIoControl(m_hPort, 0xAAAA5679L, comdevcmd, sizeof(comdevcmd),0,0,0,0)) { error("DeviceIoControl com 0xAAAA5679L"); return false; } return true; } bool SerialPort::SetSpeed(CString &speed) { if (m_hPort==0) return false; m_speed= speed; return UpdateCommParams(); } bool SerialPort::SetBits(CString &bits) { if (m_hPort==0) return false; m_bits= bits; return UpdateCommParams(); } void SerialPort::SetPort(CString &port) { if (m_device.Compare(port)==0) return; close(); m_device= port; } bool SerialPort::UpdateCommParams() { if (m_hPort==NULL) return false; DCB dcb; dcb.DCBlength= sizeof(DCB); if (!GetCommState(m_hPort, &dcb)) { error("GetCommState"); return false; } SerialParams::SetSpeed(dcb, m_speed); SerialParams::SetBits(dcb, m_bits); // todo: add flowcontrol parameters if (dcb.DCBlength != sizeof(DCB)) debug("DCB length = %04x, expected %04x\n", dcb.DCBlength, sizeof(DCB)); debug("DCB: bd=%d bin%d par%d outcts%d outdsr%d dtr%d dsr%d txcont%d outx%d inx%d errch%d nul%d rts%d abt%d dum%d\n", dcb.BaudRate, dcb.fBinary, dcb.fParity, dcb.fOutxCtsFlow, dcb.fOutxDsrFlow, dcb.fDtrControl, dcb.fDsrSensitivity, dcb.fTXContinueOnXoff, dcb.fOutX, dcb.fInX, dcb.fErrorChar, dcb.fNull, dcb.fRtsControl, dcb.fAbortOnError, dcb.fDummy2); debug("rsv=%04x xon=%04x xoff=%04x bs=%d pty=%d stop=%d xon=%02x xoff=%02x err=%02x eof=%02x evt=%02x\n", dcb.wReserved, dcb.XonLim, dcb.XoffLim, dcb.ByteSize, dcb.Parity, dcb.StopBits, dcb.XonChar, dcb.XoffChar, dcb.ErrorChar, dcb.EofChar, dcb.EvtChar); if (!SetCommState(m_hPort, &dcb)) { error("SetCommState"); return false; } return true; }