diff --git a/include/serial/impl/unix.h b/include/serial/impl/unix.h index 316634d..1674049 100644 --- a/include/serial/impl/unix.h +++ b/include/serial/impl/unix.h @@ -1,6 +1,7 @@ /*! * \file serial/impl/unix.h * \author William Woodall + * \author John Harrison * \version 0.1 * * \section LICENSE @@ -39,31 +40,43 @@ namespace serial { -class Serial::Serial_pimpl { -public: - Serial_pimpl (const std::string &port, - int baudrate, - long timeout, - bytesize_t bytesize, - parity_t parity, - stopbits_t stopbits, - flowcontrol_t flowcontrol); +using std::string; - virtual ~Serial_pimpl (); +class serial::Serial::SerialImpl { +public: + SerialImpl (const string &port, + int baudrate, + long timeout, + bytesize_t bytesize, + parity_t parity, + stopbits_t stopbits, + flowcontrol_t flowcontrol); + + virtual ~SerialImpl (); void open (); void close (); bool isOpen (); - size_t read (unsigned char* buffer, size_t size = 1); - std::string read (size_t size = 1); - size_t read (std::string &buffer, size_t size = 1); + size_t available (); + string read (size_t size = 1); + size_t write (const string &data); - size_t write (unsigned char* data, size_t length); - size_t write (const std::string &data); + void flush (); + void flushInput (); + void flushOutput (); - void setPort (const std::string &port); - std::string getPort () const; + void sendBreak(); + void setBreak(); + void setRTS(); + void setDTR(); + void getCTS(); + void getDSR(); + void getRI(); + void getCD(); + + void setPort (const string &port); + string getPort () const; void setTimeout (long timeout); long getTimeout () const; @@ -83,19 +96,26 @@ public: void setFlowcontrol (flowcontrol_t flowcontrol); flowcontrol_t getFlowcontrol () const; -private: - // Serial handle - int fd; - - // Parameters - std::string port; - int baudrate; - long timeout; - bytesize_t bytesize; - parity_t parity; - stopbits_t stopbits; - flowcontrol_t flowcontrol; +protected: + void reconfigurePort (); +private: + int fd_; // The current file descriptor. + + bool isOpen_; + + int interCharTimeout_; + int writeTimeout_; + int xonxoff_; + int rtscts_; + + string port_; // Path to the file descriptor + int baudrate_; // Baudrate + long timeout_; // Timeout for read operations + bytesize_t bytesize_; // Size of the bytes + parity_t parity_; // Parity + stopbits_t stopbits_; // Stop Bits + flowcontrol_t flowcontrol_; // Flow Control }; } diff --git a/include/serial/serial.h b/include/serial/serial.h index 376f461..c873291 100644 --- a/include/serial/serial.h +++ b/include/serial/serial.h @@ -37,8 +37,9 @@ #define SERIAL_H #include -#include // std::shared_ptr #include +#include +#include namespace serial { @@ -153,6 +154,11 @@ public: void close (); + /* Return the number of characters in the buffer. + */ + size_t + available(); + /*! Read a given amount of bytes from the serial port. * * If a timeout is set it may return less characters than requested. With @@ -166,8 +172,8 @@ public: * * \return A size_t representing the number of bytes actually read. */ - size_t - read (unsigned char* buffer, size_t size = 1); + //size_t + //read (unsigned char* buffer, size_t size = 1); /*! Read a given amount of bytes from the serial port. * @@ -182,6 +188,11 @@ public: std::string read (size_t size = 1); + std::string readline(size_t size = std::numeric_limits::max(), + std::string eol = "\n"); + + std::vector readlines(std::string eol = "\n"); + /*! Read a given amount of bytes from the serial port. * * Reads into a std::string by reference rather than returning it. @@ -193,8 +204,8 @@ public: * * \see Serial::read(size_t) */ - size_t - read (std::string &buffer, size_t size = 1); + //size_t + //read (std::string &buffer, size_t size = 1); /*! Write bytes from the data to the serial port by given length. * @@ -205,8 +216,8 @@ public: * * \return A size_t representing the number of bytes actually written. */ - size_t - write (unsigned char* data, size_t length); + //size_t + //write (unsigned char* data, size_t length); /*! Write a string to the serial port. * @@ -370,9 +381,8 @@ private: const Serial& operator=(Serial); // Pimpl idiom, d_pointer - class Serial_pimpl; - Serial_pimpl * pimpl; - + class SerialImpl; + SerialImpl *pimpl; }; class IOException : public std::exception { diff --git a/serial.cmake b/serial.cmake index e32fd3f..2d31b12 100644 --- a/serial.cmake +++ b/serial.cmake @@ -41,7 +41,7 @@ ENDIF(NOT DEFINED(LIBRARY_OUTPUT_PATH)) include_directories(${PROJECT_SOURCE_DIR}/include) # Add default source files -set(SERIAL_SRCS src/serial.cc src/serial_listener.cc) +set(SERIAL_SRCS src/serial.cc src/impl/unix.cc) # src/serial_listener.cc) # Add default header files set(SERIAL_HEADERS include/serial/serial.h include/serial/serial_listener.h) diff --git a/src/impl/unix.cc b/src/impl/unix.cc index 549aaf5..1f84a8a 100644 --- a/src/impl/unix.cc +++ b/src/impl/unix.cc @@ -1,129 +1,320 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "serial/impl/unix.h" -using namespace serial; +#ifndef TIOCINQ +#ifdef FIONREAD +#define TIOCINQ FIONREAD +#else +#define TIOCINQ 0x541B +#endif +#endif -Serial::Serial_pimpl::Serial_pimpl (const std::string &port, int baudrate, - long timeout, bytesize_t bytesize, - parity_t parity, stopbits_t stopbits, - flowcontrol_t flowcontrol) -: port(port), baudrate(baudrate), timeout(timeout), bytesize(bytesize), - parity(parity), stopbits(stopbits), flowcontrol(flowcontrol) +using ::serial::Serial; +using std::string; + +Serial::SerialImpl::SerialImpl (const string &port, int baudrate, + long timeout, bytesize_t bytesize, + parity_t parity, stopbits_t stopbits, + flowcontrol_t flowcontrol) +: fd_(-1), isOpen_(false), interCharTimeout_(-1), port_(port), + baudrate_(baudrate), timeout_(timeout), bytesize_(bytesize), + parity_(parity), stopbits_(stopbits), flowcontrol_(flowcontrol) { - this->fd = -1; + if (port_.empty() == false) this->open(); } -Serial::Serial_pimpl::~Serial_pimpl () { - if (this->isOpen()) - this->close(); +Serial::SerialImpl::~SerialImpl () { + this->close(); } void -Serial::Serial_pimpl::open () { +Serial::SerialImpl::open () { + if (port_.empty() == false) throw "error"; + if (isOpen_ == false) throw "error"; + fd_ = ::open (port_.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); + + if (fd_ == -1) throw "Error"; + + reconfigurePort(); + isOpen_ = true; } void -Serial::Serial_pimpl::close () { - this->fd = -1; +Serial::SerialImpl::reconfigurePort () { + if (fd_ == -1) throw "Error"; // Can only operate on a valid file descriptor + + struct termios options; // The current options for the file descriptor + struct termios originalTTYAttrs; // The orignal file descriptor options + + uint8_t vmin = 0, vtime = 0; // timeout is done via select + if (interCharTimeout_ == -1) { + vmin = 1; + vtime = int(interCharTimeout_ * 10); + } + + if (tcgetattr(fd_, &originalTTYAttrs) == -1) throw "Error"; + + options = originalTTYAttrs; + + // set up raw mode / no echo / binary + options.c_cflag |= (CLOCAL|CREAD); + options.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL| + ISIG|IEXTEN); //|ECHOPRT + + options.c_oflag &= ~(OPOST); + options.c_iflag &= ~(INLCR|IGNCR|ICRNL|IGNBRK); +#ifdef IUCLC + options.c_iflag &= ~IUCLC; +#endif +#ifdef PARMRK + options.c_iflag &= ~PARMRK; +#endif + + // setup baud rate + // TODO(ash_git): validate baud rate + cfsetspeed(&options, baudrate_); + + // setup char len + options.c_cflag &= ~CSIZE; + if (bytesize_ == EIGHTBITS) + options.c_cflag |= CS8; + else if (bytesize_ == SEVENBITS) + options.c_cflag |= CS7; + else if (bytesize_ == SIXBITS) + options.c_cflag |= CS6; + else if (bytesize_ == FIVEBITS) + options.c_cflag |= CS5; + else + throw "ValueError(Invalid char len: %%r)"; + // setup stopbits + if (stopbits_ == STOPBITS_ONE) + options.c_cflag &= ~(CSTOPB); + else if (stopbits_ == STOPBITS_ONE_POINT_FIVE) + options.c_cflag |= (CSTOPB); // XXX same as TWO.. there is no POSIX support for 1.5 + else if (stopbits_ == STOPBITS_TWO) + options.c_cflag |= (CSTOPB); + else + throw "ValueError(Invalid stop bit specification:)"; + // setup parity + options.c_iflag &= ~(INPCK|ISTRIP); + if (parity_ == PARITY_NONE) { + options.c_cflag &= ~(PARENB|PARODD); + } + else if (parity_ == PARITY_EVEN) { + options.c_cflag &= ~(PARODD); + options.c_cflag |= (PARENB); + } + else if (parity_ == PARITY_ODD) { + options.c_cflag |= (PARENB|PARODD); + } + else { + throw "ValueError(Invalid parity:"; + } + // setup flow control + // xonxoff +#ifdef IXANY + if (xonxoff_) + options.c_iflag |= (IXON|IXOFF); //|IXANY) + else + options.c_iflag &= ~(IXON|IXOFF|IXANY); +#else + if (xonxoff_) + options.c_iflag |= (IXON|IXOFF); + else + options.c_iflag &= ~(IXON|IXOFF); +#endif + // rtscts +#ifdef CRTSCTS + if (rtscts_) + options.c_cflag |= (CRTSCTS); + else + options.c_cflag &= ~(CRTSCTS); +#elif defined CNEW_RTSCTS + if (rtscts_) + options.c_cflag |= (CNEW_RTSCTS); + else + options.c_cflag &= ~(CNEW_RTSCTS); +#else +#error "OS Support seems wrong." +#endif + + // buffer + // vmin "minimal number of characters to be read. = for non blocking" + options.c_cc[VMIN] = vmin; + // vtime + options.c_cc[VTIME] = vtime; + + // activate settings + ::tcsetattr(fd_, TCSANOW, &options); } + +void +Serial::SerialImpl::close () { + if (isOpen_ == true) { + if (fd_ != -1) { + ::close(fd_); + fd_ = -1; + } + isOpen_ = false; + } +} + bool -Serial::Serial_pimpl::isOpen () { - return false; +Serial::SerialImpl::isOpen () { + return isOpen_; } size_t -Serial::Serial_pimpl::read (unsigned char* buffer, size_t size) { - return 0; +Serial::SerialImpl::available () { + if (!isOpen_) { + return 0; + } + int count = 0; + int result = ioctl(fd_, TIOCINQ, &count); + if (result == 0) { + return count; + } else { + throw "Error"; + } } -std::string -Serial::Serial_pimpl::read (size_t size) { - return ""; +string +Serial::SerialImpl::read (size_t size) { + if (!isOpen_) throw "PortNotOpenError()"; + string message = ""; + char buf[1024]; + fd_set readfds; + while (message.length() < size) { + FD_ZERO(&readfds); + FD_SET(fd_, &readfds); + struct timeval timeout; + timeout.tv_sec = timeout_ / 1000; + timeout.tv_usec = timeout_ % 1000; + int r = select(1, &readfds, NULL, NULL, &timeout); + + if (r == -1 && errno == EINTR) + continue; + + if (r == -1) { + perror("select()"); + exit(EXIT_FAILURE); + } + + if (FD_ISSET(fd_, &readfds)) { + memset(buf, 0, 1024); + size_t bytes_read = ::read(fd_, buf, 1024); + // read should always return some data as select reported it was + // ready to read when we get to this point. + if (bytes_read < 1) { + // Disconnected devices, at least on Linux, show the + // behavior that they are always ready to read immediately + // but reading returns nothing. + throw "SerialException('device reports readiness to read but returned no data (device disconnected?)')"; + } + message.append(buf, bytes_read); + } + else { + break; // Timeout + } + } + return message; } size_t -Serial::Serial_pimpl::read (std::string &buffer, size_t size) { - return 0; -} +Serial::SerialImpl::write (const string &data) { + if (isOpen_ == false) throw "portNotOpenError"; -size_t -Serial::Serial_pimpl::write (unsigned char* data, size_t length) { - return 0; -} - -size_t -Serial::Serial_pimpl::write (const std::string &data) { - return 0; + size_t t = data.length(); + size_t n = ::write(fd_, data.c_str(), data.length()); + if (n == -1) { + throw "Write error"; + } + return n; } void -Serial::Serial_pimpl::setPort (const std::string &port) { - +Serial::SerialImpl::setPort (const string &port) { + port_ = port; } -std::string -Serial::Serial_pimpl::getPort () const { - return this->port; +string +Serial::SerialImpl::getPort () const { + return port_; } void -Serial::Serial_pimpl::setTimeout (long timeout) { - +Serial::SerialImpl::setTimeout (long timeout) { + timeout_ = timeout; } long -Serial::Serial_pimpl::getTimeout () const { - return this->timeout; +Serial::SerialImpl::getTimeout () const { + return timeout_; } void -Serial::Serial_pimpl::setBaudrate (int baudrate) { - +Serial::SerialImpl::setBaudrate (int baudrate) { + baudrate_ = baudrate; + reconfigurePort(); } int -Serial::Serial_pimpl::getBaudrate () const { - return this->baudrate; +Serial::SerialImpl::getBaudrate () const { + return baudrate_; } void -Serial::Serial_pimpl::setBytesize (bytesize_t bytesize) { - +Serial::SerialImpl::setBytesize (serial::bytesize_t bytesize) { + bytesize_ = bytesize; } -bytesize_t -Serial::Serial_pimpl::getBytesize () const { - return this->bytesize; +serial::bytesize_t +Serial::SerialImpl::getBytesize () const { + return bytesize_; } void -Serial::Serial_pimpl::setParity (parity_t parity) { - +Serial::SerialImpl::setParity (serial::parity_t parity) { + parity_ = parity; } -parity_t -Serial::Serial_pimpl::getParity () const { - return this->parity; +serial::parity_t +Serial::SerialImpl::getParity () const { + return parity_; } void -Serial::Serial_pimpl::setStopbits (stopbits_t stopbits) { - +Serial::SerialImpl::setStopbits (serial::stopbits_t stopbits) { + stopbits_ = stopbits; } -stopbits_t -Serial::Serial_pimpl::getStopbits () const { - return this->stopbits; +serial::stopbits_t +Serial::SerialImpl::getStopbits () const { + return stopbits_; } void -Serial::Serial_pimpl::setFlowcontrol (flowcontrol_t flowcontrol) { - +Serial::SerialImpl::setFlowcontrol (serial::flowcontrol_t flowcontrol) { + flowcontrol_ = flowcontrol; } -flowcontrol_t -Serial::Serial_pimpl::getFlowcontrol () const { - return this->flowcontrol; +serial::flowcontrol_t +Serial::SerialImpl::getFlowcontrol () const { + return flowcontrol_; } diff --git a/src/serial.cc b/src/serial.cc index eb6c5c0..a975aa2 100644 --- a/src/serial.cc +++ b/src/serial.cc @@ -6,15 +6,22 @@ #include "serial/impl/unix.h" #endif -using namespace serial; +using serial::Serial; +using serial::bytesize_t; +using serial::parity_t; +using serial::stopbits_t; +using serial::flowcontrol_t; +using std::string; +using std::vector; +using std::numeric_limits; +using std::size_t; -Serial::Serial (const std::string &port, int baudrate, - long timeout, bytesize_t bytesize, - parity_t parity, stopbits_t stopbits, - flowcontrol_t flowcontrol) +Serial::Serial (const string &port, int baudrate, long timeout, + bytesize_t bytesize, parity_t parity, stopbits_t stopbits, + flowcontrol_t flowcontrol) { - pimpl = new Serial_pimpl(port,baudrate,timeout,bytesize,parity,stopbits, - flowcontrol); + pimpl = new Serial_pimpl(port, baudrate, timeout, bytesize, parity, + stopbits, flowcontrol); } Serial::~Serial () { @@ -36,36 +43,83 @@ Serial::isOpen () { } size_t -Serial::read (unsigned char* buffer, size_t size) { - return this->pimpl->read (buffer, size); +Serial::available () { + return this->pimpl->available(); } -std::string +//size_t +//Serial::read (unsigned char* buffer, size_t size) { + // return this->pimpl->read (buffer, size); +//} + +string Serial::read (size_t size) { return this->pimpl->read (size); } -size_t -Serial::read (std::string &buffer, size_t size) { - return this->pimpl->read (buffer, size); +string +Serial::readline(size_t size, string eol) { + size_t leneol = eol.length(); + string line; + while (true) { + string c = read(1); + if (c.empty()) { + line += c; + if (line.substr(line.length() - leneol, leneol) == eol) { + break; + } + if (line.length() >= size) { + break; + } + } + else { + // Timeout + break; + } + } + + return line; } -size_t -Serial::write (unsigned char* data, size_t length) { - return this->pimpl->write (data, length); +vector +Serial::readlines(string eol) { + if (this->pimpl->getTimeout() < 0) { + throw "Error, must be set for readlines"; + } + size_t leneol = eol.length(); + vector lines; + while (true) { + string line = readline(numeric_limits::max(), eol); + if (!line.empty()) { + lines.push_back(line); + if (line.substr(line.length() - leneol, leneol) == eol) + break; + } + else { + // Timeout + break; + } + } + + return lines; } +//size_t +//Serial::write (unsigned char* data, size_t length) { +// return this->pimpl->write (data, length); +//} + size_t -Serial::write (const std::string &data) { +Serial::write (const string &data) { return this->pimpl->write (data); } void -Serial::setPort (const std::string &port) { +Serial::setPort (const string &port) { this->pimpl->setPort (port); } -std::string +string Serial::getPort () const { return this->pimpl->getPort (); }