#include "stdafx.h" #include "debug.h" #include "tstmodemDlg.h" #include "serialport.h" #include "serialparams.h" #include "stringutils.h" #include #include "util/boostthread.h" SerialPort::SerialPort() : m_rcv(32768), m_snd(1024), _flog(INVALID_HANDLE_VALUE) { } SerialPort::~SerialPort() { close(); } bool SerialPort::open() { openlog(); logdata(2, &m_device[0], m_device.size()); DWORD t0= GetTickCount(); m_hPort= CreateFile(ToTString(m_device).c_str(), GENERIC_READ | GENERIC_WRITE, 0,NULL, OPEN_EXISTING, 0, NULL); if (m_hPort==NULL || m_hPort==INVALID_HANDLE_VALUE) { error("opening port %hs\n", m_device.c_str()); m_hPort= NULL; return false; } // todo: setcommtimeouts 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 %hs open in %d msec\n", m_device.c_str(), 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; } logdata(3, &m_device[0], m_device.size()); closelog(); } DWORD SerialPort::MainThreadProc(SerialPort *port) { return port->PortThreadProc(); } std::string EventString(DWORD event) { std::string str; if (event&EV_BREAK) str+= " BREAK"; if (event&EV_CTS) str+= " CTS"; if (event&EV_DSR) str+= " DSR"; if (event&EV_ERR) str+= " ERR"; if (event&EV_RING) str+= " RING"; if (event&EV_RLSD) str+= " RLSD"; if (event&EV_RXCHAR) str+= " RX"; if (event&EV_RXFLAG) str+= " RF"; if (event&EV_TXEMPTY) str+= " TX"; if (event & ~(EV_BREAK|EV_CTS|EV_DSR|EV_ERR|EV_RING|EV_RLSD|EV_RXCHAR|EV_TXEMPTY|EV_RXFLAG) ) str+= " ??"; return str; } std::string ModemStatusString(DWORD modem) { std::string str; if (modem&MS_CTS_ON) str+= " CTS"; if (modem&MS_DSR_ON) str+= " DSR"; if (modem&MS_RING_ON) str+= " RING"; if (modem&MS_RLSD_ON) str+= " RLSD"; if (modem& ~(MS_CTS_ON|MS_DSR_ON|MS_RING_ON|MS_RLSD_ON) ) str+= " ??"; 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 %hs, modem=%hs\n", EventString(event).c_str(), ModemStatusString(modem).c_str()); 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; } // t-h == size (mod maxbuf ), size!=maxbuf, head!=maxbuf, tail!=maxbuf // 0.....t....h....m : wanted=m-h // 0.....h....t....m : wanted=t-h DWORD nWanted= std::min(m_rcv.maxbuf-m_rcv.size, m_rcv.maxbuf-m_rcv.head); if (!ReadFile(m_hPort, m_rcv.buf+m_rcv.head, nWanted, &nRead, NULL)) { error("ReadFile"); return; } logdata(0, m_rcv.buf+m_rcv.head, nRead); m_rcv.head+=nRead; m_rcv.size+=nRead; if (m_rcv.head==m_rcv.maxbuf) m_rcv.head= 0; 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(std::string& str) { str.clear(); boost::mutex::scoped_lock lock(m_rcvmtx); DWORD t0= GetTickCount(); // wait until data is available, or timeout. while (!m_rcv.size && GetTickCount()-t0<200) m_rcvcond.timed_wait(lock, 200); str.resize(m_rcv.size); int copied=0; // this should loop once or twice. while (m_rcv.size) { // t-h == size (mod maxbuf ), size!=0, head!=maxbuf, tail!=maxbuf // 0.....t....h....m : wanted=h-t // 0.....h....t....m : wanted=m-t DWORD nWanted= std::min(m_rcv.size, m_rcv.maxbuf-m_rcv.tail); memcpy(&str[copied], m_rcv.buf+m_rcv.tail, nWanted); m_rcv.size-=nWanted; m_rcv.tail+=nWanted; if (m_rcv.tail==m_rcv.maxbuf) m_rcv.tail= 0; copied+=nWanted; } str.resize(copied); return str.size(); } /* 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 { DWORD nWanted= std::min(m_snd.size, m_snd.maxbuf-m_snd.tail); if (!WriteFile(m_hPort, m_snd.buf+m_snd.tail, nWanted, &nWritten, NULL)) { error("WriteFile"); return; } logdata(1, m_snd.buf+m_snd.tail, nWritten); m_snd.tail+=nWritten; if (m_snd.tail==m_snd.maxbuf) m_snd.tail= 0; m_snd.size-=nWritten; nTotalWritten+=nWritten; } while (nWritten>0 && m_snd.size>0); m_sndcond.notify_one(); if (nTotalWritten) { //debug("wrote %d bytes\n", nTotalWritten); } } bool SerialPort::SendData(const std::string& str) { if (m_hPort==0) return false; //debug("%8d : sending %4d bytes: %ls\n", t0, buflen, str.GetBuffer(0)); boost::mutex::scoped_lock lock(m_sndmtx); // wait until sufficient xmit buffer space is available. DWORD t0= GetTickCount(); while (m_snd.maxbuf-m_snd.sizeCommEventOutputOverFlow(); 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(const std::string &speed) { if (m_hPort==0) return false; m_speed= speed; return UpdateCommParams(); } bool SerialPort::SetBits(const std::string &bits) { if (m_hPort==0) return false; m_bits= bits; return UpdateCommParams(); } void SerialPort::SetPort(const std::string &port) { if (m_device==port) 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; } void SerialPort::openlog() { _flog= CreateFile(_T("\\com.log"), GENERIC_WRITE, FILE_SHARE_READ,NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (_flog==INVALID_HANDLE_VALUE) { error("CreateFile(com.log)"); //return false; } SetFilePointer(_flog, 0, 0, SEEK_END); //return true; } void SerialPort::closelog() { if (_flog!=INVALID_HANDLE_VALUE) CloseHandle(_flog); } void SerialPort::logdata(int dir, const void *buf, int size) { if (_flog==INVALID_HANDLE_VALUE) return; ByteVector v(size+8); *((DWORD*)&v[0])= GetTickCount(); *((WORD*)&v[4])= dir; *((WORD*)&v[6])= size; if (size) memcpy(&v[8], buf, size); DWORD nWritten=0; WriteFile(_flog, &v[0], v.size(), &nWritten, 0); FlushFileBuffers(_flog); }