diff --git a/cmSerialPort.c b/cmSerialPort.c new file mode 100644 index 0000000..35fb037 --- /dev/null +++ b/cmSerialPort.c @@ -0,0 +1,562 @@ +#include "cmPrefix.h" +#include "cmGlobal.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmCtx.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmSerialPort.h" +#include "cmThread.h" + +#include +#include +#include // close() +#include // O_RDWR +#include // TIOCEXCL + +typedef struct cmSerialPort_str +{ + cmErr_t _err; + cmThreadH_t _thH; + const char* _deviceStr; + int _deviceH; + unsigned _baudRate; + unsigned _cfgFlags; + cmSeCallbackFunc_t _cbFunc; + void* _cbArg; + struct termios _ttyAttrs; + struct pollfd _pollfd; + unsigned _pollPeriodMs; +} cmSerialPort_t; + +cmSerialPort_t* _cmSePtrFromHandle( cmSeH_t h ) +{ + cmSerialPort_t* p = (cmSerialPort_t*)h.h; + assert(p!=NULL); + return p; +} + +void _cmSeSetClosedState( cmSerialPort_t* p ) +{ + if( p->_deviceStr != NULL ) + cmMemFree((char*)(p->_deviceStr)); + + p->_deviceH = -1; + p->_deviceStr = NULL; + p->_baudRate = 0; + p->_cfgFlags = 0; + p->_cbFunc = NULL; + p->_cbArg = NULL; + +} + +cmSeRC_t _cmSeGetAttributes( cmSerialPort_t* p, struct termios* attr ) +{ + if( tcgetattr(p->_deviceH, attr) == -1 ) + return cmErrSysMsg(&p->_err,kGetAttrFailSeRC,errno,"Error getting tty attributes from %s.",p->_deviceStr); + + return kOkSeRC; +} + +cmSeRC_t _cmSePoll( cmSerialPort_t* p, unsigned timeOutMs ) +{ + cmSeRC_t rc = kOkSeRC; + int sysRC; + + if((sysRC = poll(&p->_pollfd,1,timeOutMs)) == 0) + rc = kTimeOutSeRC; + else + { + if( sysRC < 0 ) + rc = cmErrSysMsg(&p->_err,kReadFailSeRC,errno,"Poll failed on serial port."); + } + + return rc; + +} + +bool _cmSeThreadFunc(void* param) +{ + cmSerialPort_t* p = (cmSerialPort_t*)param; + cmSeH_t h; + h.h = p; + unsigned readN; + if( cmSeIsOpen(h) ) + cmSeReceiveCbTimeOut(h,p->_pollPeriodMs,&readN); + + + + return true; +} + + +cmSeRC_t _cmSeDestroy( cmSerialPort_t* p ) +{ + cmSeRC_t rc = kOkSeRC; + + + // stop the thread first + if( cmThreadDestroy(&p->_thH) != kOkThRC ) + { + rc = cmErrMsg(&p->_err,kThreadErrSeRC,"Thread destroy failed."); + goto errLabel; + } + + // Block until all written output has been sent from the device. + // Note that this call is simply passed on to the serial device driver. + // See tcsendbreak(3) ("man 3 tcsendbreak") for details. + if (tcdrain(p->_deviceH) == -1) + { + rc = cmErrSysMsg(&p->_err,kFlushFailSeRC,errno,"Error waiting for serial device '%s' to drain.", p->_deviceStr ); + goto errLabel; + } + + // It is good practice to reset a serial port back to the state in + // which you found it. This is why we saved the original termios struct + // The constant TCSANOW (defined in termios.h) indicates that + // the change should take effect immediately. + + if (tcsetattr(p->_deviceH, TCSANOW, &p->_ttyAttrs) == -1) + { + rc = cmErrSysMsg(&p->_err,kSetAttrFailSeRC,errno,"Error resetting tty attributes on serial device '%s'.",p->_deviceStr); + goto errLabel; + } + + if( p->_deviceH != -1 ) + { + if( close(p->_deviceH ) != 0 ) + { + rc = cmErrSysMsg(&p->_err,kCloseFailSeRC,errno,"Port close failed on serial dvice '%s'.", p->_deviceStr); + goto errLabel; + } + + _cmSeSetClosedState(p); + } + + cmMemPtrFree(&p); + + errLabel: + return rc; +} + + +cmSeH_t cmSeCreate( cmCtx_t* ctx, cmSeH_t* hp, const char* deviceStr, unsigned baudRate, unsigned cfgFlags, cmSeCallbackFunc_t cbFunc, void* cbArg, unsigned pollPeriodMs ) +{ + cmSeRC_t rc = kOkSeRC; + struct termios options; + cmSeH_t h; + + // if the port is already open then close it + if((rc = cmSeDestroy(hp)) != kOkSeRC ) + return *hp; + + cmSerialPort_t* p = cmMemAllocZ(cmSerialPort_t,1); + + cmErrSetup(&p->_err,&ctx->rpt,"Serial Port"); + + p->_deviceH = -1; + + // open the port + if( (p->_deviceH = open(deviceStr, O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1 ) + { + rc = cmErrSysMsg(&p->_err,kOpenFailSeRC,errno,"Error opening serial '%s'",cmStringNullGuard(deviceStr)); + goto errLabel;; + } + + // Note that open() follows POSIX semantics: multiple open() calls to + // the same file will succeed unless the TIOCEXCL ioctl is issued. + // This will prevent additional opens except by root-owned processes. + // See tty(4) ("man 4 tty") and ioctl(2) ("man 2 ioctl") for details. + + if( ioctl(p->_deviceH, TIOCEXCL) == -1 ) + { + rc = cmErrSysMsg(&p->_err,kResourceNotAvailableSeRC,errno,"The serial device '%s' is already in use.", cmStringNullGuard(deviceStr)); + goto errLabel; + } + + + // Now that the device is open, clear the O_NONBLOCK flag so + // subsequent I/O will block. + // See fcntl(2) ("man 2 fcntl") for details. + /* + if (fcntl(_deviceH, F_SETFL, 0) == -1) + + { + _error("Error clearing O_NONBLOCK %s - %s(%d).", pr.devFilePath.c_str(), strerror(errno), errno); + goto errLabel; + } + */ + + // Get the current options and save them so we can restore the + // default settings later. + if (tcgetattr(p->_deviceH, &p->_ttyAttrs) == -1) + { + rc = cmErrSysMsg(&p->_err,kGetAttrFailSeRC,errno,"Error getting tty attributes from the device '%s'.",deviceStr); + goto errLabel; + } + + + // The serial port attributes such as timeouts and baud rate are set by + // modifying the termios structure and then calling tcsetattr to + // cause the changes to take effect. Note that the + // changes will not take effect without the tcsetattr() call. + // See tcsetattr(4) ("man 4 tcsetattr") for details. + options = p->_ttyAttrs; + + + // Set raw input (non-canonical) mode, with reads blocking until either + // a single character has been received or a 100ms timeout expires. + // See tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios") + // for details. + cfmakeraw(&options); + options.c_cc[VMIN] = 1; + options.c_cc[VTIME] = 1; + + + // The baud rate, word length, and handshake options can be set as follows: + + + // set baud rate + cfsetspeed(&options, baudRate); + + options.c_cflag |= CREAD | CLOCAL; // ignore modem controls + + // set data word size + cmClrBits(options.c_cflag, CSIZE); // clear the word size bits + cmEnaBits(options.c_cflag, CS5, cmIsFlag(cfgFlags, kDataBits5SeFl)); + cmEnaBits(options.c_cflag, CS6, cmIsFlag(cfgFlags, kDataBits6SeFl)); + cmEnaBits(options.c_cflag, CS7, cmIsFlag(cfgFlags, kDataBits7SeFl)); + cmEnaBits(options.c_cflag, CS8, cmIsFlag(cfgFlags, kDataBits8SeFl)); + + cmClrBits(options.c_cflag, PARENB); // assume no-parity + + // if the odd or even parity flag is set + if( cmIsFlag( cfgFlags, kEvenParitySeFl) || cmIsFlag( cfgFlags, kOddParitySeFl ) ) + { + cmSetBits(options.c_cflag, PARENB); + + if( cmIsFlag(cfgFlags, kOddParitySeFl ) ) + cmSetBits( options.c_cflag, PARODD); + } + + // set two stop bits + cmEnaBits( options.c_cflag, CSTOPB, cmIsFlag(cfgFlags, k2StopBitSeFl)); + + + // set hardware flow control + //cmEnaBits(options.c_cflag, CCTS_OFLOW, cmIsFlag(cfgFlags, kCTS_OutFlowCtlFl)); + //cmEnaBits(options.c_cflag, CRTS_IFLOW, cmIsFlag(cfgFlags, kRTS_InFlowCtlFl)); + //cmEnaBits(options.c_cflag, CDTR_IFLOW, cmIsFlag(cfgFlags, kDTR_InFlowCtlFl)); + //cmEnaBits(options.c_cflag, CDSR_OFLOW, cmIsFlag(cfgFlags, kDSR_OutFlowCtlFl)); + //cmEnaBits(options.c_cflag, CCAR_OFLOW, cmIsFlag(cfgFlags, kDCD_OutFlowCtlFl)); + + cmClrBits(options.c_cflag,CRTSCTS); // turn-off hardware flow control + + // 7 bit words, enable even parity, CTS out ctl flow, RTS in ctl flow + // note: set PARODD and PARENB to enable odd parity) + //options.c_cflag |= (CS7 | PARENB | CCTS_OFLOW | CRTS_IFLOW ); + + // Cause the new options to take effect immediately. + if (tcsetattr(p->_deviceH, TCSANOW, &options) == -1) + { + + rc = cmErrSysMsg(&p->_err,kSetAttrFailSeRC,errno,"Error setting tty attributes on serial device %.", deviceStr); + goto errLabel; + } + + memset(&p->_pollfd,0,sizeof(p->_pollfd)); + p->_pollfd.fd = p->_deviceH; + p->_pollfd.events = POLLIN; + + p->_deviceStr = cmMemAllocStr( deviceStr ); + p->_baudRate = baudRate; + p->_cfgFlags = cfgFlags; + p->_cbFunc = cbFunc; + p->_cbArg = cbArg; + p->_pollPeriodMs = pollPeriodMs; + + // create the listening thread + if( cmThreadCreate( &p->_thH, _cmSeThreadFunc, p, &ctx->rpt) != kOkThRC ) + { + rc = cmErrMsg(&p->_err,kThreadErrSeRC,"Thread initialization failed."); + goto errLabel; + } + + if( cmThreadPause(p->_thH,0) != kOkThRC ) + { + rc = cmErrMsg(&p->_err,kThreadErrSeRC,0,"Thread start failed."); + goto errLabel; + } + + if( hp != NULL ) + hp->h = p; + else + h.h = p; + + errLabel: + if( rc != kOkSeRC ) + { + _cmSeDestroy(p); + h.h = NULL; + } + + return hp != NULL ? *hp : h; + +} + +cmSeRC_t cmSeDestroy(cmSeH_t* hp ) +{ + cmSeRC_t rc = kOkSeRC; + + if( hp==NULL || !cmSeIsOpen(*hp) ) + return kOkSeRC; + + cmSerialPort_t* p = _cmSePtrFromHandle(*hp); + + if((rc = _cmSeDestroy(p)) != kOkSeRC ) + return rc; + + hp->h = NULL; + + return rc; +} + +bool cmSeIsOpen( cmSeH_t h) +{ + if( h.h == NULL ) + return false; + + cmSerialPort_t* p = _cmSePtrFromHandle(h); + return p->_deviceH != -1; +} + +cmSeRC_t cmSeSend( cmSeH_t h, const void* byteA, unsigned byteN ) +{ + cmSeRC_t rc = kOkSeRC; + cmSerialPort_t* p = _cmSePtrFromHandle(h); + + if( !cmSeIsOpen(h) ) + return cmErrWarnMsg( &p->_err, kResourceNotAvailableSeRC, "An attempt was made to transmit from a closed serial port."); + + if( byteN == 0 ) + return rc; + + // implement a non blocking write - if less than all the bytes were written then iterate + unsigned i = 0; + do + { + int n = 0; + if((n = write( p->_deviceH, ((char*)byteA)+i, byteN-i )) == -1 ) + { + rc = cmErrSysMsg(&p->_err,kWriteFailSeRC,errno,"Write failed on serial port '%s'.", p->_deviceStr ); + break; + } + + i += n; + + + }while( i 0 && p->_cbFunc != NULL ) + p->_cbFunc( p->_cbArg, buf, *readN_Ref ); + + return rc; + +} + +cmSeRC_t cmSeReceiveCbTimeOut( cmSeH_t h, unsigned timeOutMs, unsigned* readN_Ref) +{ + cmSeRC_t rc; + cmSerialPort_t* p = _cmSePtrFromHandle(h); + + if((rc = _cmSePoll(p,timeOutMs)) == kOkSeRC ) + rc = cmSeReceiveCbNb(h,readN_Ref); + return rc; + +} + +cmSeRC_t cmSeReceiveNb( cmSeH_t h, void* buf, unsigned bufN, unsigned* readN_Ref) +{ + cmSeRC_t rc = kOkSeRC; + cmSerialPort_t* p = _cmSePtrFromHandle(h); + + if( readN_Ref != NULL ) + *readN_Ref = 0; + + if( !cmSeIsOpen(h) ) + return cmErrWarnMsg(&p->_err, kResourceNotAvailableSeRC, "An attempt was made to read from a closed serial port."); + + int n = 0; + + // if attempt to read the port succeeded ... + if((n =read( p->_deviceH, buf, bufN )) != -1 ) + *readN_Ref = n; + else + { + // ... or failed and it wasn't because the port was empty + if( errno != EAGAIN) + rc = cmErrSysMsg(&p->_err,kReadFailSeRC,errno,"An attempt to read the serial port '%s' failed.", p->_deviceStr ); + } + + return rc; +} + +cmSeRC_t cmSeReceive( cmSeH_t h, void* buf, unsigned bufByteN, unsigned timeOutMs, unsigned* readN_Ref ) +{ + cmSeRC_t rc = kOkSeRC; + cmSerialPort_t* p = _cmSePtrFromHandle(h); + + if((rc = _cmSePoll(p,timeOutMs)) == kOkSeRC ) + rc = cmSeReceiveNb(h,buf,bufByteN,readN_Ref); + + return rc; +} + +const char* cmSeDevice( cmSeH_t h) +{ + cmSerialPort_t* p = _cmSePtrFromHandle(h); + return p->_deviceStr; +} + +unsigned cmSeBaudRate( cmSeH_t h) +{ + cmSerialPort_t* p = _cmSePtrFromHandle(h); + return p->_baudRate; +} + +unsigned cmSeCfgFlags( cmSeH_t h) +{ + cmSerialPort_t* p = _cmSePtrFromHandle(h); + return p->_cfgFlags; +} + +unsigned cmSeReadInBaudRate( cmSeH_t h ) +{ + struct termios attr; + cmSerialPort_t* p = _cmSePtrFromHandle(h); + + if((_cmSeGetAttributes(p,&attr)) != kOkSeRC ) + return 0; + + return cfgetispeed(&attr); + +} + +unsigned cmSeReadOutBaudRate( cmSeH_t h) +{ + struct termios attr; + cmSerialPort_t* p = _cmSePtrFromHandle(h); + + if((_cmSeGetAttributes(p,&attr)) != kOkSeRC ) + return 0; + + return cfgetospeed(&attr); + +} + +unsigned cmSeReadCfgFlags( cmSeH_t h) +{ + struct termios attr; + unsigned result = 0; + cmSerialPort_t* p = _cmSePtrFromHandle(h); + + if((_cmSeGetAttributes(p,&attr)) == false ) + return 0; + + switch( attr.c_cflag & CSIZE ) + { + case CS5: + cmSetBits( result, kDataBits5SeFl); + break; + + case CS6: + cmSetBits( result, kDataBits6SeFl ); + break; + + case CS7: + cmSetBits( result, kDataBits7SeFl); + break; + + case CS8: + cmSetBits( result, kDataBits8SeFl); + break; + } + + cmEnaBits( result, k2StopBitSeFl, cmIsFlag( attr.c_cflag, CSTOPB )); + cmEnaBits( result, k1StopBitSeFl, !cmIsFlag( attr.c_cflag, CSTOPB )); + + if( cmIsFlag( attr.c_cflag, PARENB ) ) + { + cmEnaBits( result, kOddParitySeFl, cmIsFlag( attr.c_cflag, PARODD )); + cmEnaBits( result, kEvenParitySeFl, !cmIsFlag( attr.c_cflag, PARODD )); + } + + return result; + +} + + +//==================================================================================================== +// +// + + +void _cmSePortTestCb( void* arg, const void* byteA, unsigned byteN ) +{ + const char* text = (const char*)byteA; + + for(unsigned i=0; i