diff --git a/.gitignore b/.gitignore index 00f5a05..d63ff7e 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ patched wiped msg_gen srv_gen +doc/html +*sublime-workspace diff --git a/CMakeLists.txt b/CMakeLists.txt index dfffc80..d6f9e6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.4.6) set(ROS_ROOT $ENV{ROS_ROOT}) -option(SERIAL_BUILD_WIHOUT_ROS "Build without ROS?" OFF) +option(SERIAL_BUILD_WIHOUT_ROS "Build without ROS?" ON) if(DEFINED ROS_ROOT AND NOT SERIAL_BUILD_WIHOUT_ROS) # Build with ROS diff --git a/README.md b/README.md index 75bd0fe..b0d903c 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Run the example: The BSD License -Copyright (c) 2011 William Woodall +Copyright (c) 2012 William Woodall Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/doc/Doxyfile b/doc/Doxyfile index f64a659..5704725 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -37,7 +37,7 @@ PROJECT_NUMBER = 1.0 # for a project that appears at the top of each page and should give viewer # a quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = Cross-platform serial port library for C++ +PROJECT_BRIEF = "Cross-platform serial port library for C++" # With the PROJECT_LOGO tag one can specify an logo or icon that is # included in the documentation. The maximum height of the logo should not @@ -610,7 +610,7 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = include/serial include/serial/impl src src/impl +INPUT = include src # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is @@ -634,7 +634,7 @@ FILE_PATTERNS = # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. -RECURSIVE = NO +RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a diff --git a/include/serial/impl/unix.h b/include/serial/impl/unix.h index 98f0fec..6700a24 100644 --- a/include/serial/impl/unix.h +++ b/include/serial/impl/unix.h @@ -8,9 +8,9 @@ * * The MIT License * - * Copyright (c) 2011 William Woodall, John Harrison + * Copyright (c) 2012 William Woodall, John Harrison * - * Permission is hereby granted, free of charge, to any person obtaining a + * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, @@ -24,8 +24,8 @@ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * \section DESCRIPTION @@ -54,7 +54,6 @@ class serial::Serial::SerialImpl { public: SerialImpl (const string &port, unsigned long baudrate, - long timeout, bytesize_t bytesize, parity_t parity, stopbits_t stopbits, @@ -75,10 +74,10 @@ public: available (); size_t - read (unsigned char* buf, size_t size = 1); + read (uint8_t *buf, size_t size = 1); size_t - write (const string &data); + write (const uint8_t *data, size_t length); void flush (); @@ -90,28 +89,31 @@ public: flushOutput (); void - sendBreak(int duration); + sendBreak (int duration); void - setBreak(bool level); + setBreak (bool level); void - setRTS(bool level); + setRTS (bool level); void - setDTR(bool level); + setDTR (bool level); bool - getCTS(); + waitForChange (); bool - getDSR(); + getCTS (); bool - getRI(); + getDSR (); bool - getCD(); + getRI (); + + bool + getCD (); void setPort (const string &port); @@ -120,9 +122,9 @@ public: getPort () const; void - setTimeout (long timeout); + setTimeout (Timeout &timeout); - long + Timeout getTimeout () const; void @@ -156,16 +158,16 @@ public: getFlowcontrol () const; void - readLock(); + readLock (); void - readUnlock(); + readUnlock (); void - writeLock(); + writeLock (); void - writeUnlock(); + writeUnlock (); protected: void reconfigurePort (); @@ -178,7 +180,7 @@ private: bool xonxoff_; bool rtscts_; - long timeout_; // Timeout for read operations + Timeout timeout_; // Timeout for read operations unsigned long baudrate_; // Baudrate parity_t parity_; // Parity diff --git a/include/serial/impl/windows.h b/include/serial/impl/win.h similarity index 81% rename from include/serial/impl/windows.h rename to include/serial/impl/win.h index aa06706..902a70a 100644 --- a/include/serial/impl/windows.h +++ b/include/serial/impl/win.h @@ -8,7 +8,7 @@ * * The MIT License * - * Copyright (c) 2011 William Woodall, John Harrison + * Copyright (c) 2012 William Woodall, John Harrison * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -39,6 +39,8 @@ #include "serial/serial.h" +#include "windows.h" + namespace serial { using std::string; @@ -51,7 +53,6 @@ class serial::Serial::SerialImpl { public: SerialImpl (const string &port, unsigned long baudrate, - long timeout, bytesize_t bytesize, parity_t parity, stopbits_t stopbits, @@ -72,10 +73,10 @@ public: available (); size_t - read (char* buf, size_t size = 1); + read (uint8_t *buf, size_t size = 1); size_t - write (const string &data); + write (const uint8_t *data, size_t length); void flush (); @@ -87,50 +88,53 @@ public: flushOutput (); void - sendBreak(int duration); + sendBreak (int duration); void - setBreak(bool level); + setBreak (bool level); void - setRTS(bool level); + setRTS (bool level); void - setDTR(bool level); - + setDTR (bool level); + bool - getCTS(); - + waitForChange (); + bool - getDSR(); - + getCTS (); + bool - getRI(); - + getDSR (); + bool - getCD(); + getRI (); + + bool + getCD (); void setPort (const string &port); - + string getPort () const; void - setTimeout (long timeout); - - long + setTimeout (Timeout &timeout); + + Timeout getTimeout () const; void setBaudrate (unsigned long baudrate); - + unsigned long getBaudrate () const; void setBytesize (bytesize_t bytesize); - + bytesize_t getBytesize () const; @@ -152,24 +156,39 @@ public: flowcontrol_t getFlowcontrol () const; + void + readLock (); + + void + readUnlock (); + + void + writeLock (); + + void + writeUnlock (); + protected: void reconfigurePort (); private: string port_; // Path to the file descriptor - int fd_; // The current file descriptor + HANDLE fd_; - bool isOpen_; - bool xonxoff_; - bool rtscts_; + bool is_open_; - long timeout_; // Timeout for read operations + Timeout timeout_; // Timeout for read operations unsigned long baudrate_; // Baudrate parity_t parity_; // Parity bytesize_t bytesize_; // Size of the bytes stopbits_t stopbits_; // Stop Bits flowcontrol_t flowcontrol_; // Flow Control + + // Mutex used to lock the read functions + HANDLE read_mutex; + // Mutex used to lock the write functions + HANDLE write_mutex; }; } diff --git a/include/serial/serial.h b/include/serial/serial.h index c1a9269..fa128cf 100644 --- a/include/serial/serial.h +++ b/include/serial/serial.h @@ -8,7 +8,7 @@ * * The MIT License * - * Copyright (c) 2011 William Woodall + * Copyright (c) 2012 William Woodall * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -38,12 +38,15 @@ #include #include -#include +#include +#include #include #include #include +#include -#define THROW(exceptionClass, message) throw exceptionClass(__FILE__, __LINE__, (message) ) +#define THROW(exceptionClass, message) throw exceptionClass(__FILE__, \ +__LINE__, (message) ) namespace serial { @@ -83,27 +86,70 @@ typedef enum { flowcontrol_software } flowcontrol_t; +/*! + * Structure for setting the timeout of the serial port, times are + * in milliseconds. + * + * In order to disable the interbyte timeout, set it to Timeout::max(). + */ +struct Timeout { + static uint32_t max() {return std::numeric_limits::max();} + /*! + * Convenience function to generate Timeout structs using a + * single absolute timeout. + * + * \param timeout A long that defines the time in milliseconds until a + * timeout occurs after a call to read or write is made. + * + * \return Timeout struct that represents this simple timeout provided. + */ + static Timeout simpleTimeout(uint32_t timeout) { + return Timeout(max(), timeout, 0, timeout, 0); + } + + /*! Number of milliseconds between bytes received to timeout on. */ + uint32_t inter_byte_timeout; + /*! A constant number of milliseconds to wait after calling read. */ + uint32_t read_timeout_constant; + /*! A multiplier against the number of requested bytes to wait after + * calling read. + */ + uint32_t read_timeout_multiplier; + /*! A constant number of milliseconds to wait after calling write. */ + uint32_t write_timeout_constant; + /*! A multiplier against the number of requested bytes to wait after + * calling write. + */ + uint32_t write_timeout_multiplier; + + Timeout (uint32_t inter_byte_timeout_=0, uint32_t read_timeout_constant_=0, + uint32_t read_timeout_multiplier_=0, uint32_t write_timeout_constant_=0, + uint32_t write_timeout_multiplier_=0) + : inter_byte_timeout(inter_byte_timeout_), + read_timeout_constant(read_timeout_constant_), + read_timeout_multiplier(read_timeout_multiplier_), + write_timeout_constant(write_timeout_constant_), + write_timeout_multiplier(write_timeout_multiplier_) + {} +}; + /*! * Class that provides a portable serial port interface. */ class Serial { public: /*! - * Constructor, creates a SerialPortBoost object and opens the port. + * Creates a Serial object and opens the port if a port is specified, + * otherwise it remains closed until serial::Serial::open is called. * * \param port A std::string containing the address of the serial port, * which would be something like 'COM1' on Windows and '/dev/ttyS0' * on Linux. * - * \param baudrate An integer that represents the buadrate + * \param baudrate An unsigned 32-bit integer that represents the baudrate * - * \param timeout A long that represents the time (in milliseconds) until a - * timeout on reads occur. Setting this to zero (0) will cause reading to - * be non-blocking, i.e. the available data will be returned immediately, - * but it will not block to wait for more. Setting this to a number less - * than zero (-1) will result in infinite blocking behaviour, i.e. the - * serial port will block until either size bytes have been read or an - * exception has occured. + * \param timeout A serial::Timeout struct that defines the timeout + * conditions for the serial port. \see serial::Timeout * * \param bytesize Size of each byte in the serial transmission of data, * default is eightbits, possible values are: fivebits, sixbits, sevenbits, @@ -119,14 +165,11 @@ public: * flowcontrol_none, possible values are: flowcontrol_none, * flowcontrol_software, flowcontrol_hardware * - * \param buffer_size The maximum size of the internal buffer, defaults - * to 256 bytes (2^8). - * * \throw PortNotOpenedException */ Serial (const std::string &port = "", - unsigned long baudrate = 9600, - long timeout = 0, + uint32_t baudrate = 9600, + Timeout timeout = Timeout(), bytesize_t bytesize = eightbits, parity_t parity = parity_none, stopbits_t stopbits = stopbits_one, @@ -136,8 +179,8 @@ public: virtual ~Serial (); /*! - * Opens the serial port as long as the portname is set and the port isn't - * alreay open. + * Opens the serial port as long as the port is set and the port isn't + * already open. * * If the port is provided to the constructor then an explicit call to open * is not needed. @@ -164,28 +207,82 @@ public: /*! Return the number of characters in the buffer. */ size_t - available(); + available (); - /*! Read a given amount of bytes from the serial port. - * - * If a timeout is set it may return less characters than requested. With - * no timeout it will block until the requested number of bytes have been - * read or until an exception occurs. + /*! Read a given amount of bytes from the serial port into a given buffer. * + * The read function will return in one of three cases: + * * The number of requested bytes was read. + * * In this case the number of bytes requested will match the size_t + * returned by read. + * * A timeout occurred, in this case the number of bytes read will not + * match the amount requested, but no exception will be thrown. One of + * two possible timeouts occurred: + * * The inter byte timeout expired, this means that number of + * milliseconds elapsed between receiving bytes from the serial port + * exceeded the inter byte timeout. + * * The total timeout expired, which is calculated by multiplying the + * read timeout multiplier by the number of requested bytes and then + * added to the read timeout constant. If that total number of + * milliseconds elapses after the initial call to read a timeout will + * occur. + * * An exception occurred, in this case an actual exception will be thrown. + * + * \param buffer An uint8_t array of at least the requested size. * \param size A size_t defining how many bytes to be read. * - * \return A std::string containing the data read. + * \return A size_t representing the number of bytes read as a result of the + * call to read. */ size_t - read (unsigned char *buffer, size_t size); + read (uint8_t *buffer, size_t size); + + /*! Read a given amount of bytes from the serial port into a give buffer. + * + * \param buffer A reference to a std::vector of uint8_t. + * \param size A size_t defining how many bytes to be read. + * + * \return A size_t representing the number of bytes read as a result of the + * call to read. + */ size_t - read (std::vector &buffer, size_t size = 1); + read (std::vector &buffer, size_t size = 1); + + /*! Read a given amount of bytes from the serial port into a give buffer. + * + * \param buffer A reference to a std::string. + * \param size A size_t defining how many bytes to be read. + * + * \return A size_t representing the number of bytes read as a result of the + * call to read. + */ size_t read (std::string &buffer, size_t size = 1); + + /*! Read a given amount of bytes from the serial port and return a string + * containing the data. + * + * \param size A size_t defining how many bytes to be read. + * + * \return A std::string containing the data read from the port. + */ std::string read (size_t size = 1); - /*! Reads in a line or until a given delimiter has been processed + /*! Reads in a line or until a given delimiter has been processed. + * + * Reads from the serial port until a single line has been read. + * + * \param buffer A std::string reference used to store the data. + * \param size A maximum length of a line, defaults to 65536 (2^16) + * \param eol A string to match against for the EOL. + * + * \return A size_t representing the number of bytes read. + */ + size_t + readline (std::string &buffer, size_t size = 65536, std::string eol = "\n"); + + /*! Reads in a line or until a given delimiter has been processed. * * Reads from the serial port until a single line has been read. * @@ -194,15 +291,10 @@ public: * * \return A std::string containing the line. */ - size_t - readline (std::string &buffer, - size_t size = 65536, - std::string eol = "\n"); std::string - readline (size_t size = 65536, - std::string eol = "\n"); + readline (size_t size = 65536, std::string eol = "\n"); - /*! Reads in multiple lines until the serail port times out. + /*! Reads in multiple lines until the serial port times out. * * This requires a timeout > 0 before it can be run. It will read until a * timeout occurs and return a list of strings. @@ -218,160 +310,220 @@ public: /*! Write a string to the serial port. * - * \param data A const reference containg the data to be written + * \param data A const reference containing the data to be written + * to the serial port. + * + * \param size A size_t that indicates how many bytes should be written from + * the given data buffer. + * + * \return A size_t representing the number of bytes actually written to + * the serial port. + */ + size_t + write (const uint8_t *data, size_t size); + + /*! Write a string to the serial port. + * + * \param data A const reference containing the data to be written * to the serial port. * * \return A size_t representing the number of bytes actually written to * the serial port. */ size_t - write (const unsigned char *data, size_t size); - size_t - write (const std::vector &data); + write (const std::vector &data); + + /*! Write a string to the serial port. + * + * \param data A const reference containing the data to be written + * to the serial port. + * + * \return A size_t representing the number of bytes actually written to + * the serial port. + */ size_t write (const std::string &data); /*! Sets the serial port identifier. - * - * \param port A const std::string reference containing the address of the - * serial port, which would be something like 'COM1' on Windows and - * '/dev/ttyS0' on Linux. - * - * \throw InvalidConfigurationException - */ + * + * \param port A const std::string reference containing the address of the + * serial port, which would be something like 'COM1' on Windows and + * '/dev/ttyS0' on Linux. + * + * \throw InvalidConfigurationException + */ void setPort (const std::string &port); /*! Gets the serial port identifier. - * - * \see Serial::setPort - * - * \throw InvalidConfigurationException - */ + * + * \see Serial::setPort + * + * \throw InvalidConfigurationException + */ std::string getPort () const; - /*! Sets the timeout for reads in milliseconds. - * - * \param timeout A long that represents the time (in milliseconds) until a - * timeout on reads occur. Setting this to zero (0) will cause reading to be - * non-blocking, i.e. the available data will be returned immediately, but it - * will not block to wait for more. Setting this to a number less than - * zero (-1) will result in infinite blocking behaviour, i.e. the serial port - * will block until either size bytes have been read or an exception has - * occured. - */ + /*! Sets the timeout for reads and writes using the Timeout struct. + * + * There are two timeout conditions described here: + * * The inter byte timeout: + * * The inter_byte_timeout component of serial::Timeout defines the + * maximum amount of time, in milliseconds, between receiving bytes on + * the serial port that can pass before a timeout occurs. Setting this + * to zero will prevent inter byte timeouts from occurring. + * * Total time timeout: + * * The the constant and multiplier component of this timeout condition, + * for both read and write, are defined in serial::Timeout. This + * timeout occurs if the total time since the read or write call was + * made exceeds the specified time in milliseconds. + * * The limit is defined by multiplying the multiplier component by the + * number of requested bytes and adding that product to the constant + * component. In this way if you want a read call, for example, to + * timeout after exactly one second regardless of the number of bytes + * you asked for then set the read_timeout_constant component of + * serial::Timeout to 1000 and the read_timeout_multiplier to zero. + * This timeout condition can be used in conjunction with the inter + * byte timeout condition with out any problems, timeout will simply + * occur when one of the two timeout conditions is met. This allows + * users to have maximum control over the trade-off between + * responsiveness and efficiency. + * + * Read and write functions will return in one of three cases. When the + * reading or writing is complete, when a timeout occurs, or when an + * exception occurs. + * + * \param timeout A serial::Timeout struct containing the inter byte + * timeout, and the read and write timeout constants and multipliers. + * + * \see serial::Timeout + */ void - setTimeout (long timeout); + setTimeout (Timeout &timeout); + + /*! Sets the timeout for reads and writes. */ + void + setTimeout (uint32_t inter_byte_timeout, uint32_t read_timeout_constant, + uint32_t read_timeout_multiplier, uint32_t write_timeout_constant, + uint32_t write_timeout_multiplier) + { + Timeout timeout(inter_byte_timeout, read_timeout_constant, + read_timeout_multiplier, write_timeout_constant, + write_timeout_multiplier); + return setTimeout(timeout); + } /*! Gets the timeout for reads in seconds. - * - * \see Serial::setTimeout - */ - long + * + * \return A Timeout struct containing the inter_byte_timeout, and read + * and write timeout constants and multipliers. + * + * \see Serial::setTimeout + */ + Timeout getTimeout () const; /*! Sets the baudrate for the serial port. - * - * Possible baudrates depends on the system but some safe baudrates include: - * 110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 56000, - * 57600, 115200 - * Some other baudrates that are supported by some comports: - * 128000, 153600, 230400, 256000, 460800, 921600 - * - * \param baudrate An integer that sets the baud rate for the serial port. - * - * \throw InvalidConfigurationException - */ + * + * Possible baudrates depends on the system but some safe baudrates include: + * 110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 56000, + * 57600, 115200 + * Some other baudrates that are supported by some comports: + * 128000, 153600, 230400, 256000, 460800, 921600 + * + * \param baudrate An integer that sets the baud rate for the serial port. + * + * \throw InvalidConfigurationException + */ void - setBaudrate (unsigned long baudrate); + setBaudrate (uint32_t baudrate); /*! Gets the baudrate for the serial port. - * - * \return An integer that sets the baud rate for the serial port. - * - * \see Serial::setBaudrate - * - * \throw InvalidConfigurationException - */ - unsigned long + * + * \return An integer that sets the baud rate for the serial port. + * + * \see Serial::setBaudrate + * + * \throw InvalidConfigurationException + */ + uint32_t getBaudrate () const; /*! Sets the bytesize for the serial port. - * - * \param bytesize Size of each byte in the serial transmission of data, - * default is eightbits, possible values are: fivebits, sixbits, sevenbits, - * eightbits - * - * \throw InvalidConfigurationException - */ + * + * \param bytesize Size of each byte in the serial transmission of data, + * default is eightbits, possible values are: fivebits, sixbits, sevenbits, + * eightbits + * + * \throw InvalidConfigurationException + */ void setBytesize (bytesize_t bytesize); /*! Gets the bytesize for the serial port. - * - * \see Serial::setBytesize - * - * \throw InvalidConfigurationException - */ + * + * \see Serial::setBytesize + * + * \throw InvalidConfigurationException + */ bytesize_t getBytesize () const; /*! Sets the parity for the serial port. - * - * \param parity Method of parity, default is parity_none, possible values - * are: parity_none, parity_odd, parity_even - * - * \throw InvalidConfigurationException - */ + * + * \param parity Method of parity, default is parity_none, possible values + * are: parity_none, parity_odd, parity_even + * + * \throw InvalidConfigurationException + */ void setParity (parity_t parity); /*! Gets the parity for the serial port. - * - * \see Serial::setParity - * - * \throw InvalidConfigurationException - */ + * + * \see Serial::setParity + * + * \throw InvalidConfigurationException + */ parity_t getParity () const; /*! Sets the stopbits for the serial port. - * - * \param stopbits Number of stop bits used, default is stopbits_one, - * possible values are: stopbits_one, stopbits_one_point_five, stopbits_two - * - * \throw InvalidConfigurationException - */ + * + * \param stopbits Number of stop bits used, default is stopbits_one, + * possible values are: stopbits_one, stopbits_one_point_five, stopbits_two + * + * \throw InvalidConfigurationException + */ void setStopbits (stopbits_t stopbits); /*! Gets the stopbits for the serial port. - * - * \see Serial::setStopbits - * - * \throw InvalidConfigurationException - */ + * + * \see Serial::setStopbits + * + * \throw InvalidConfigurationException + */ stopbits_t getStopbits () const; /*! Sets the flow control for the serial port. - * - * \param flowcontrol Type of flowcontrol used, default is flowcontrol_none, - * possible values are: flowcontrol_none, flowcontrol_software, - * flowcontrol_hardware - * - * \throw InvalidConfigurationException - */ + * + * \param flowcontrol Type of flowcontrol used, default is flowcontrol_none, + * possible values are: flowcontrol_none, flowcontrol_software, + * flowcontrol_hardware + * + * \throw InvalidConfigurationException + */ void setFlowcontrol (flowcontrol_t flowcontrol); /*! Gets the flow control for the serial port. - * - * \see Serial::setFlowcontrol - * - * \throw InvalidConfigurationException - */ + * + * \see Serial::setFlowcontrol + * + * \throw InvalidConfigurationException + */ flowcontrol_t getFlowcontrol () const; @@ -387,27 +539,52 @@ public: void flushOutput (); + /*! Sends the RS-232 break signal. See tcsendbreak(3). */ void sendBreak (int duration); + /*! Set the break condition to a given level. Defaults to true. */ void setBreak (bool level = true); + /*! Set the RTS handshaking line to the given level. Defaults to true. */ void setRTS (bool level = true); + /*! Set the DTR handshaking line to the given level. Defaults to true. */ void setDTR (bool level = true); + /*! + * Blocks until CTS, DSR, RI, CD changes or something interrupts it. + * + * Can throw an exception if an error occurs while waiting. + * You can check the status of CTS, DSR, RI, and CD once this returns. + * Uses TIOCMIWAIT via ioctl if available (mostly only on Linux) with a + * resolution of less than +-1ms and as good as +-0.2ms. Otherwise a + * polling method is used which can give +-2ms. + * + * \return Returns true if one of the lines changed, false if something else + * occurred. + * + * \throw SerialException + */ + bool + waitForChange (); + + /*! Returns the current status of the CTS line. */ bool getCTS (); + /*! Returns the current status of the DSR line. */ bool getDSR (); + /*! Returns the current status of the RI line. */ bool getRI (); + /*! Returns the current status of the CD line. */ bool getCD (); @@ -429,15 +606,24 @@ private: // Read common function size_t - read_ (unsigned char *buffer, size_t size); + read_ (uint8_t *buffer, size_t size); + // Write common function + size_t + write_ (const uint8_t *data, size_t length); }; class SerialExecption : public std::exception { + // Disable copy constructors + void operator=(const SerialExecption&); + const SerialExecption& operator=(SerialExecption); const char* e_what_; public: SerialExecption (const char *description) : e_what_ (description) {} + SerialExecption (const SerialExecption& other) { + e_what_ = other.e_what_; + } virtual const char* what () const throw () { @@ -449,6 +635,9 @@ public: class IOException : public std::exception { + // Disable copy constructors + void operator=(const IOException&); + const IOException& operator=(IOException); std::string file_; int line_; const char* e_what_; @@ -459,6 +648,9 @@ public: explicit IOException (std::string file, int line, const char * description) : file_(file), line_(line), e_what_ (description), errno_(0) {} virtual ~IOException() throw() {} + IOException (const IOException& other) { + e_what_ = other.e_what_; + } int getErrorNumber () { return errno_; } @@ -476,9 +668,15 @@ public: class PortNotOpenedException : public std::exception { + // Disable copy constructors + void operator=(const PortNotOpenedException&); + const PortNotOpenedException& operator=(PortNotOpenedException); const char * e_what_; public: PortNotOpenedException (const char * description) : e_what_ (description) {} + PortNotOpenedException (const PortNotOpenedException& other) { + e_what_ = other.e_what_; + } virtual const char* what () const throw () { @@ -488,11 +686,6 @@ public: } }; -class SerialExceptionBase : public std::exception -{ - -}; - } // namespace serial #endif diff --git a/include/serial/v8stdint.h b/include/serial/v8stdint.h new file mode 100644 index 0000000..ca0db6b --- /dev/null +++ b/include/serial/v8stdint.h @@ -0,0 +1,57 @@ +// This header is from the v8 google project: +// http://code.google.com/p/v8/source/browse/trunk/include/v8stdint.h + +// Copyright 2012 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Load definitions of standard types. + +#ifndef V8STDINT_H_ +#define V8STDINT_H_ + +#include +#include + +#if defined(_WIN32) && !defined(__MINGW32__) + +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; // NOLINT +typedef unsigned short uint16_t; // NOLINT +typedef int int32_t; +typedef unsigned int uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +// intptr_t and friends are defined in crtdefs.h through stdio.h. + +#else + +#include + +#endif + +#endif // V8STDINT_H_ diff --git a/serial.cmake b/serial.cmake index ed63438..1f422cd 100644 --- a/serial.cmake +++ b/serial.cmake @@ -122,6 +122,7 @@ macro(build_serial) ) INSTALL(FILES include/serial/serial.h + include/serial/v8stdint.h DESTINATION include/serial) IF(NOT CMAKE_FIND_INSTALL_PATH) diff --git a/serial.sublime-project b/serial.sublime-project new file mode 100644 index 0000000..65e92d9 --- /dev/null +++ b/serial.sublime-project @@ -0,0 +1,20 @@ +{ + "word_wrap": "on", + "wrap_width": 80, + "folders": + [ + { + "path": "/Users/william/devel/serial" + } + ], + "settings": + { + "sublimeclang_options": + [ + "-I/usr/include", + "-I/usr/local/include", + // "-I/usr/lib/clang/3.1/include/", + "-I${folder:${project_path:serial.sublime-project}}/include" + ] + } +} diff --git a/src/impl/unix.cc b/src/impl/unix.cc index e87125d..962fd56 100644 --- a/src/impl/unix.cc +++ b/src/impl/unix.cc @@ -2,9 +2,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -35,6 +37,7 @@ #endif using std::string; +using std::stringstream; using std::invalid_argument; using serial::Serial; using serial::SerialExecption; @@ -43,12 +46,12 @@ using serial::IOException; Serial::SerialImpl::SerialImpl (const string &port, unsigned long baudrate, - long timeout, bytesize_t bytesize, + bytesize_t bytesize, parity_t parity, stopbits_t stopbits, flowcontrol_t flowcontrol) -: port_ (port), fd_ (-1), is_open_ (false), xonxoff_ (true), rtscts_ (false), - timeout_ (timeout), baudrate_ (baudrate), parity_ (parity), - bytesize_ (bytesize), stopbits_ (stopbits), flowcontrol_ (flowcontrol) + : port_ (port), fd_ (-1), is_open_ (false), xonxoff_ (true), rtscts_ (false), + baudrate_ (baudrate), parity_ (parity), + bytesize_ (bytesize), stopbits_ (stopbits), flowcontrol_ (flowcontrol) { pthread_mutex_init(&this->read_mutex, NULL); pthread_mutex_init(&this->write_mutex, NULL); @@ -66,30 +69,26 @@ Serial::SerialImpl::~SerialImpl () void Serial::SerialImpl::open () { - if (port_.empty ()) - { + if (port_.empty ()) { throw invalid_argument ("Empty port is invalid."); } - if (is_open_ == true) - { + if (is_open_ == true) { throw SerialExecption ("Serial port already open."); } fd_ = ::open (port_.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); - if (fd_ == -1) - { - switch (errno) - { - case EINTR: - // Recurse because this is a recoverable error. - open (); - return; - case ENFILE: - case EMFILE: - THROW (IOException, "Too many file handles open."); - default: - THROW (IOException, errno); + if (fd_ == -1) { + switch (errno) { + case EINTR: + // Recurse because this is a recoverable error. + open (); + return; + case ENFILE: + case EMFILE: + THROW (IOException, "Too many file handles open."); + default: + THROW (IOException, errno); } } @@ -100,26 +99,24 @@ Serial::SerialImpl::open () void Serial::SerialImpl::reconfigurePort () { - if (fd_ == -1) - { + if (fd_ == -1) { // Can only operate on a valid file descriptor THROW (IOException, "Invalid file descriptor, is the serial port open?"); } struct termios options; // The options for the file descriptor - if (tcgetattr(fd_, &options) == -1) - { + if (tcgetattr(fd_, &options) == -1) { THROW (IOException, "::tcgetattr"); } // set up raw mode / no echo / binary - options.c_cflag |= (unsigned long) (CLOCAL|CREAD); - options.c_lflag &= (unsigned long) ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL| - ISIG|IEXTEN); //|ECHOPRT + options.c_cflag |= (unsigned long) (CLOCAL | CREAD); + options.c_lflag &= (unsigned long) ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL | + ISIG | IEXTEN); //|ECHOPRT options.c_oflag &= (unsigned long) ~(OPOST); - options.c_iflag &= (unsigned long) ~(INLCR|IGNCR|ICRNL|IGNBRK); + options.c_iflag &= (unsigned long) ~(INLCR | IGNCR | ICRNL | IGNBRK); #ifdef IUCLC options.c_iflag &= (unsigned long) ~IUCLC; #endif @@ -130,122 +127,118 @@ Serial::SerialImpl::reconfigurePort () // setup baud rate bool custom_baud = false; speed_t baud; - switch (baudrate_) - { + switch (baudrate_) { #ifdef B0 - case 0: baud = B0; break; + case 0: baud = B0; break; #endif #ifdef B50 - case 50: baud = B50; break; + case 50: baud = B50; break; #endif #ifdef B75 - case 75: baud = B75; break; + case 75: baud = B75; break; #endif #ifdef B110 - case 110: baud = B110; break; + case 110: baud = B110; break; #endif #ifdef B134 - case 134: baud = B134; break; + case 134: baud = B134; break; #endif #ifdef B150 - case 150: baud = B150; break; + case 150: baud = B150; break; #endif #ifdef B200 - case 200: baud = B200; break; + case 200: baud = B200; break; #endif #ifdef B300 - case 300: baud = B300; break; + case 300: baud = B300; break; #endif #ifdef B600 - case 600: baud = B600; break; + case 600: baud = B600; break; #endif #ifdef B1200 - case 1200: baud = B1200; break; + case 1200: baud = B1200; break; #endif #ifdef B1800 - case 1800: baud = B1800; break; + case 1800: baud = B1800; break; #endif #ifdef B2400 - case 2400: baud = B2400; break; + case 2400: baud = B2400; break; #endif #ifdef B4800 - case 4800: baud = B4800; break; + case 4800: baud = B4800; break; #endif #ifdef B7200 - case 7200: baud = B7200; break; + case 7200: baud = B7200; break; #endif #ifdef B9600 - case 9600: baud = B9600; break; + case 9600: baud = B9600; break; #endif #ifdef B14400 - case 14400: baud = B14400; break; + case 14400: baud = B14400; break; #endif #ifdef B19200 - case 19200: baud = B19200; break; + case 19200: baud = B19200; break; #endif #ifdef B28800 - case 28800: baud = B28800; break; + case 28800: baud = B28800; break; #endif #ifdef B57600 - case 57600: baud = B57600; break; + case 57600: baud = B57600; break; #endif #ifdef B76800 - case 76800: baud = B76800; break; + case 76800: baud = B76800; break; #endif #ifdef B38400 - case 38400: baud = B38400; break; + case 38400: baud = B38400; break; #endif #ifdef B115200 - case 115200: baud = B115200; break; + case 115200: baud = B115200; break; #endif #ifdef B128000 - case 128000: baud = B128000; break; + case 128000: baud = B128000; break; #endif #ifdef B153600 - case 153600: baud = B153600; break; + case 153600: baud = B153600; break; #endif #ifdef B230400 - case 230400: baud = B230400; break; + case 230400: baud = B230400; break; #endif #ifdef B256000 - case 256000: baud = B256000; break; + case 256000: baud = B256000; break; #endif #ifdef B460800 - case 460800: baud = B460800; break; + case 460800: baud = B460800; break; #endif #ifdef B921600 - case 921600: baud = B921600; break; + case 921600: baud = B921600; break; #endif - default: - custom_baud = true; -// Mac OS X 10.x Support + default: + custom_baud = true; + // Mac OS X 10.x Support #if defined(__APPLE__) && defined(__MACH__) #define IOSSIOSPEED _IOW('T', 2, speed_t) - int new_baud = static_cast (baudrate_); - if (ioctl (fd_, IOSSIOSPEED, &new_baud, 1) < 0) - { - THROW (IOException, errno); - } -// Linux Support + int new_baud = static_cast (baudrate_); + if (ioctl (fd_, IOSSIOSPEED, &new_baud, 1) < 0) { + THROW (IOException, errno); + } + // Linux Support #elif defined(__linux__) - struct serial_struct ser; - ioctl (fd_, TIOCGSERIAL, &ser); - // set custom divisor - ser.custom_divisor = ser.baud_base / baudrate_; - // update flags - ser.flags &= ~ASYNC_SPD_MASK; - ser.flags |= ASYNC_SPD_CUST; + struct serial_struct ser; + ioctl (fd_, TIOCGSERIAL, &ser); + // set custom divisor + ser.custom_divisor = ser.baud_base / baudrate_; + // update flags + ser.flags &= ~ASYNC_SPD_MASK; + ser.flags |= ASYNC_SPD_CUST; - if (ioctl (fd_, TIOCSSERIAL, ser) < 0) - { - THROW (IOException, errno); - } + if (ioctl (fd_, TIOCSSERIAL, ser) < 0) { + THROW (IOException, errno); + } #else - throw invalid_argument ("OS does not currently support custom bauds"); + throw invalid_argument ("OS does not currently support custom bauds"); #endif } - if (custom_baud == false) - { + if (custom_baud == false) { #ifdef _BSD_SOURCE ::cfsetspeed(&options, baud); #else @@ -257,56 +250,49 @@ Serial::SerialImpl::reconfigurePort () // setup char len options.c_cflag &= (unsigned long) ~CSIZE; if (bytesize_ == eightbits) - options.c_cflag |= CS8; + options.c_cflag |= CS8; else if (bytesize_ == sevenbits) - options.c_cflag |= CS7; + options.c_cflag |= CS7; else if (bytesize_ == sixbits) - options.c_cflag |= CS6; + options.c_cflag |= CS6; else if (bytesize_ == fivebits) - options.c_cflag |= CS5; + options.c_cflag |= CS5; else - throw invalid_argument ("invalid char len"); + throw invalid_argument ("invalid char len"); // setup stopbits if (stopbits_ == stopbits_one) - options.c_cflag &= (unsigned long) ~(CSTOPB); + options.c_cflag &= (unsigned long) ~(CSTOPB); else if (stopbits_ == stopbits_one_point_five) - // ONE POINT FIVE same as TWO.. there is no POSIX support for 1.5 - options.c_cflag |= (CSTOPB); + // ONE POINT FIVE same as TWO.. there is no POSIX support for 1.5 + options.c_cflag |= (CSTOPB); else if (stopbits_ == stopbits_two) - options.c_cflag |= (CSTOPB); + options.c_cflag |= (CSTOPB); else - throw invalid_argument ("invalid stop bit"); + throw invalid_argument ("invalid stop bit"); // setup parity - options.c_iflag &= (unsigned long) ~(INPCK|ISTRIP); - if (parity_ == parity_none) - { - options.c_cflag &= (unsigned long) ~(PARENB|PARODD); - } - else if (parity_ == parity_even) - { + options.c_iflag &= (unsigned long) ~(INPCK | ISTRIP); + if (parity_ == parity_none) { + options.c_cflag &= (unsigned long) ~(PARENB | PARODD); + } else if (parity_ == parity_even) { options.c_cflag &= (unsigned long) ~(PARODD); options.c_cflag |= (PARENB); - } - else if (parity_ == parity_odd) - { - options.c_cflag |= (PARENB|PARODD); - } - else - { + } else if (parity_ == parity_odd) { + options.c_cflag |= (PARENB | PARODD); + } else { throw invalid_argument ("invalid parity"); } // setup flow control // xonxoff #ifdef IXANY if (xonxoff_) - options.c_iflag |= (IXON|IXOFF); //|IXANY) + options.c_iflag |= (IXON | IXOFF); //|IXANY) else - options.c_iflag &= (unsigned long) ~(IXON|IXOFF|IXANY); + options.c_iflag &= (unsigned long) ~(IXON | IXOFF | IXANY); #else if (xonxoff_) - options.c_iflag |= (IXON|IXOFF); + options.c_iflag |= (IXON | IXOFF); else - options.c_iflag &= (unsigned long) ~(IXON|IXOFF); + options.c_iflag &= (unsigned long) ~(IXON | IXOFF); #endif // rtscts #ifdef CRTSCTS @@ -324,8 +310,8 @@ Serial::SerialImpl::reconfigurePort () #endif // http://www.unixwiz.net/techtips/termios-vmin-vtime.html - // this basically sets the read call up to be a polling read, - // but we are using select to ensure there is data available + // this basically sets the read call up to be a polling read, + // but we are using select to ensure there is data available // to read before each call, so we should never needlessly poll options.c_cc[VMIN] = 0; options.c_cc[VTIME] = 0; @@ -337,10 +323,8 @@ Serial::SerialImpl::reconfigurePort () void Serial::SerialImpl::close () { - if (is_open_ == true) - { - if (fd_ != -1) - { + if (is_open_ == true) { + if (fd_ != -1) { ::close (fd_); // Ignoring the outcome fd_ = -1; } @@ -357,23 +341,20 @@ Serial::SerialImpl::isOpen () const size_t Serial::SerialImpl::available () { - if (!is_open_) - { + if (!is_open_) { return 0; } int count = 0; int result = ioctl (fd_, TIOCINQ, &count); - if (result == 0) - { + if (result == 0) { return static_cast (count); - } - else - { + } else { THROW (IOException, errno); } } -inline void get_time_now(struct timespec &time) { +inline void get_time_now(struct timespec &time) +{ # ifdef __MACH__ // OS X does not have clock_gettime, use clock_get_time clock_serv_t cclock; mach_timespec_t mts; @@ -388,53 +369,70 @@ inline void get_time_now(struct timespec &time) { } size_t -Serial::SerialImpl::read (unsigned char* buf, size_t size) +Serial::SerialImpl::read (uint8_t *buf, size_t size) { - if (!is_open_) - { + // If the port is not open, throw + if (!is_open_) { throw PortNotOpenedException ("Serial::read"); } fd_set readfds; size_t bytes_read = 0; - struct timeval timeout; - timeout.tv_sec = timeout_ / 1000; - timeout.tv_usec = static_cast (timeout_ % 1000) * 1000; - while (bytes_read < size) - { + // Setup the total_timeout timeval + // This timeout is maximum time before a timeout after read is called + struct timeval total_timeout; + // Calculate total timeout in milliseconds t_c + (t_m * N) + long total_timeout_ms = timeout_.read_timeout_constant; + total_timeout_ms += timeout_.read_timeout_multiplier*static_cast(size); + total_timeout.tv_sec = total_timeout_ms / 1000; + total_timeout.tv_usec = static_cast(total_timeout_ms % 1000); + total_timeout.tv_usec *= 1000; // To convert to micro seconds + // Setup the inter byte timeout + struct timeval inter_byte_timeout; + inter_byte_timeout.tv_sec = timeout_.inter_byte_timeout / 1000; + inter_byte_timeout.tv_usec = + static_cast (timeout_.inter_byte_timeout % 1000); + inter_byte_timeout.tv_usec *= 1000; // To convert to micro seconds + while (bytes_read < size) { + // Setup the select timeout timeval + struct timeval timeout; + // If the total_timeout is less than the inter_byte_timeout + if (total_timeout.tv_sec < inter_byte_timeout.tv_sec + || (total_timeout.tv_sec == inter_byte_timeout.tv_sec + && total_timeout.tv_usec < inter_byte_timeout.tv_sec)) + { + // Then set the select timeout to use the total time + timeout = total_timeout; + } else { + // Else set the select timeout to use the inter byte time + timeout = inter_byte_timeout; + } FD_ZERO (&readfds); FD_SET (fd_, &readfds); - // On Linux the timeout struct is updated by select to contain the time - // left on the timeout to make looping easier, but on other platforms this - // does not occur. -#if !defined(__linux__) // Begin timing select struct timespec start, end; - get_time_now(start); -#endif - // Do the select + get_time_now (start); + // Call select to block for serial data or a timeout int r = select (fd_ + 1, &readfds, NULL, NULL, &timeout); -#if !defined(__linux__) // Calculate difference and update the structure - get_time_now(end); + get_time_now (end); // Calculate the time select took struct timeval diff; - diff.tv_sec = end.tv_sec-start.tv_sec; - diff.tv_usec = static_cast ((end.tv_nsec-start.tv_nsec)/1000); + diff.tv_sec = end.tv_sec - start.tv_sec; + diff.tv_usec = static_cast ((end.tv_nsec - start.tv_nsec) / 1000); // Update the timeout - if (timeout.tv_sec <= diff.tv_sec) { - timeout.tv_sec = 0; + if (total_timeout.tv_sec <= diff.tv_sec) { + total_timeout.tv_sec = 0; } else { - timeout.tv_sec -= diff.tv_sec; + total_timeout.tv_sec -= diff.tv_sec; } - if (timeout.tv_usec <= diff.tv_usec) { - timeout.tv_usec = 0; + if (total_timeout.tv_usec <= diff.tv_usec) { + total_timeout.tv_usec = 0; } else { - timeout.tv_usec -= diff.tv_usec; + total_timeout.tv_usec -= diff.tv_usec; } -#endif // Figure out what happened by looking at select's response 'r' -/** Error **/ + /** Error **/ if (r < 0) { // Select was interrupted, try again if (errno == EINTR) { @@ -443,22 +441,21 @@ Serial::SerialImpl::read (unsigned char* buf, size_t size) // Otherwise there was some error THROW (IOException, errno); } -/** Timeout **/ + /** Timeout **/ if (r == 0) { break; } -/** Something ready to read **/ + /** Something ready to read **/ if (r > 0) { // Make sure our file descriptor is in the ready to read list if (FD_ISSET (fd_, &readfds)) { - // This should be non-blocking returning only what is avaialble now + // This should be non-blocking returning only what is available now // Then returning so that select can block again. ssize_t bytes_read_now = - ::read (fd_, buf+bytes_read, size-bytes_read); + ::read (fd_, buf + bytes_read, size - bytes_read); // read should always return some data as select reported it was // ready to read when we get to this point. - if (bytes_read_now < 1) - { + if (bytes_read_now < 1) { // Disconnected devices, at least on Linux, show the // behavior that they are always ready to read immediately // but reading returns nothing. @@ -484,21 +481,111 @@ Serial::SerialImpl::read (unsigned char* buf, size_t size) } // This shouldn't happen, if r > 0 our fd has to be in the list! THROW (IOException, "select reports ready to read, but our fd isn't" - " in the list, this shouldn't happen!"); + " in the list, this shouldn't happen!"); } } return bytes_read; } size_t -Serial::SerialImpl::write (const string &data) +Serial::SerialImpl::write (const uint8_t *data, size_t length) { - if (is_open_ == false) - { + if (is_open_ == false) { throw PortNotOpenedException ("Serial::write"); } + fd_set writefds; + size_t bytes_written = 0; + struct timeval timeout; + timeout.tv_sec = timeout_.write_timeout_constant / 1000; + timeout.tv_usec = static_cast (timeout_.write_timeout_multiplier % 1000); + timeout.tv_usec *= 1000; // To convert to micro seconds + while (bytes_written < length) { + FD_ZERO (&writefds); + FD_SET (fd_, &writefds); + // On Linux the timeout struct is updated by select to contain the time + // left on the timeout to make looping easier, but on other platforms this + // does not occur. +#if !defined(__linux__) + // Begin timing select + struct timespec start, end; + get_time_now(start); +#endif + // Do the select + int r = select (fd_ + 1, &writefds, NULL, NULL, &timeout); +#if !defined(__linux__) + // Calculate difference and update the structure + get_time_now(end); + // Calculate the time select took + struct timeval diff; + diff.tv_sec = end.tv_sec - start.tv_sec; + diff.tv_usec = static_cast ((end.tv_nsec - start.tv_nsec) / 1000); + // Update the timeout + if (timeout.tv_sec <= diff.tv_sec) { + timeout.tv_sec = 0; + } else { + timeout.tv_sec -= diff.tv_sec; + } + if (timeout.tv_usec <= diff.tv_usec) { + timeout.tv_usec = 0; + } else { + timeout.tv_usec -= diff.tv_usec; + } +#endif - return static_cast (::write (fd_, data.c_str (), data.length ())); + // Figure out what happened by looking at select's response 'r' + /** Error **/ + if (r < 0) { + // Select was interrupted, try again + if (errno == EINTR) { + continue; + } + // Otherwise there was some error + THROW (IOException, errno); + } + /** Timeout **/ + if (r == 0) { + break; + } + /** Something ready to read **/ + if (r > 0) { + // Make sure our file descriptor is in the ready to read list + if (FD_ISSET (fd_, &writefds)) { + // This should be non-blocking returning only what is avaialble now + // Then returning so that select can block again. + ssize_t bytes_written_now = + ::write (fd_, data + bytes_written, length - bytes_written); + // read should always return some data as select reported it was + // ready to read when we get to this point. + if (bytes_written_now < 1) { + // Disconnected devices, at least on Linux, show the + // behavior that they are always ready to read immediately + // but reading returns nothing. + throw SerialExecption ("device reports readiness to write but " + "returned no data (device disconnected?)"); + } + // Update bytes_read + bytes_written += static_cast (bytes_written_now); + // If bytes_read == size then we have read everything we need + if (bytes_written == length) { + break; + } + // If bytes_read < size then we have more to read + if (bytes_written < length) { + continue; + } + // If bytes_read > size then we have over read, which shouldn't happen + if (bytes_written > length) { + throw SerialExecption ("read over read, too many bytes where " + "read, this shouldn't happen, might be " + "a logical error!"); + } + } + // This shouldn't happen, if r > 0 our fd has to be in the list! + THROW (IOException, "select reports ready to read, but our fd isn't" + " in the list, this shouldn't happen!"); + } + } + return bytes_written; } void @@ -514,12 +601,12 @@ Serial::SerialImpl::getPort () const } void -Serial::SerialImpl::setTimeout (long timeout) +Serial::SerialImpl::setTimeout (serial::Timeout &timeout) { timeout_ = timeout; } -long +serial::Timeout Serial::SerialImpl::getTimeout () const { return timeout_; @@ -598,8 +685,7 @@ Serial::SerialImpl::getFlowcontrol () const void Serial::SerialImpl::flush () { - if (is_open_ == false) - { + if (is_open_ == false) { throw PortNotOpenedException ("Serial::flush"); } tcdrain (fd_); @@ -608,8 +694,7 @@ Serial::SerialImpl::flush () void Serial::SerialImpl::flushInput () { - if (is_open_ == false) - { + if (is_open_ == false) { throw PortNotOpenedException ("Serial::flushInput"); } tcflush (fd_, TCIFLUSH); @@ -618,8 +703,7 @@ Serial::SerialImpl::flushInput () void Serial::SerialImpl::flushOutput () { - if (is_open_ == false) - { + if (is_open_ == false) { throw PortNotOpenedException ("Serial::flushOutput"); } tcflush (fd_, TCOFLUSH); @@ -628,25 +712,21 @@ Serial::SerialImpl::flushOutput () void Serial::SerialImpl::sendBreak (int duration) { - if (is_open_ == false) - { + if (is_open_ == false) { throw PortNotOpenedException ("Serial::sendBreak"); } - tcsendbreak (fd_, static_cast (duration/4)); + tcsendbreak (fd_, static_cast (duration / 4)); } void Serial::SerialImpl::setBreak (bool level) { - if (is_open_ == false) - { + if (is_open_ == false) { throw PortNotOpenedException ("Serial::setBreak"); } - if (level) - { + if (level) { ioctl (fd_, TIOCSBRK); - } - else { + } else { ioctl (fd_, TIOCCBRK); } } @@ -654,15 +734,12 @@ Serial::SerialImpl::setBreak (bool level) void Serial::SerialImpl::setRTS (bool level) { - if (is_open_ == false) - { + if (is_open_ == false) { throw PortNotOpenedException ("Serial::setRTS"); } - if (level) - { + if (level) { ioctl (fd_, TIOCMBIS, TIOCM_RTS); - } - else { + } else { ioctl (fd_, TIOCMBIC, TIOCM_RTS); } } @@ -670,25 +747,45 @@ Serial::SerialImpl::setRTS (bool level) void Serial::SerialImpl::setDTR (bool level) { - if (is_open_ == false) - { + if (is_open_ == false) { throw PortNotOpenedException ("Serial::setDTR"); } - if (level) - { + if (level) { ioctl (fd_, TIOCMBIS, TIOCM_DTR); - } - else - { + } else { ioctl (fd_, TIOCMBIC, TIOCM_DTR); } } +bool +Serial::SerialImpl::waitForChange () +{ +#ifndef TIOCMIWAIT + while (is_open_ == true) { + int s = ioctl (fd_, TIOCMGET, 0); + if ((s & TIOCM_CTS) != 0) return true; + if ((s & TIOCM_DSR) != 0) return true; + if ((s & TIOCM_RI) != 0) return true; + if ((s & TIOCM_CD) != 0) return true; + usleep(1000); + } +#else + if (ioctl(fd_, TIOCMIWAIT, (TIOCM_CD|TIOCM_DSR|TIOCM_RI|TIOCM_CTS)) != 0) { + stringstream ss; + ss << "waitForDSR failed on a call to ioctl(TIOCMIWAIT): " + << errno << " " << strerror(errno); + throw(SerialExecption(ss.str().c_str())); + return false; + } + return true; +#endif + return false; +} + bool Serial::SerialImpl::getCTS () { - if (is_open_ == false) - { + if (is_open_ == false) { throw PortNotOpenedException ("Serial::getCTS"); } int s = ioctl (fd_, TIOCMGET, 0); @@ -696,10 +793,9 @@ Serial::SerialImpl::getCTS () } bool -Serial::SerialImpl::getDSR() +Serial::SerialImpl::getDSR () { - if (is_open_ == false) - { + if (is_open_ == false) { throw PortNotOpenedException ("Serial::getDSR"); } int s = ioctl (fd_, TIOCMGET, 0); @@ -707,10 +803,9 @@ Serial::SerialImpl::getDSR() } bool -Serial::SerialImpl::getRI() +Serial::SerialImpl::getRI () { - if (is_open_ == false) - { + if (is_open_ == false) { throw PortNotOpenedException ("Serial::getRI"); } int s = ioctl (fd_, TIOCMGET, 0); @@ -718,10 +813,9 @@ Serial::SerialImpl::getRI() } bool -Serial::SerialImpl::getCD() +Serial::SerialImpl::getCD () { - if (is_open_ == false) - { + if (is_open_ == false) { throw PortNotOpenedException ("Serial::getCD"); } int s = ioctl (fd_, TIOCMGET, 0); @@ -729,7 +823,8 @@ Serial::SerialImpl::getCD() } void -Serial::SerialImpl::readLock() { +Serial::SerialImpl::readLock () +{ int result = pthread_mutex_lock(&this->read_mutex); if (result) { THROW (IOException, result); @@ -737,7 +832,8 @@ Serial::SerialImpl::readLock() { } void -Serial::SerialImpl::readUnlock() { +Serial::SerialImpl::readUnlock () +{ int result = pthread_mutex_unlock(&this->read_mutex); if (result) { THROW (IOException, result); @@ -745,7 +841,8 @@ Serial::SerialImpl::readUnlock() { } void -Serial::SerialImpl::writeLock() { +Serial::SerialImpl::writeLock () +{ int result = pthread_mutex_lock(&this->write_mutex); if (result) { THROW (IOException, result); @@ -753,7 +850,8 @@ Serial::SerialImpl::writeLock() { } void -Serial::SerialImpl::writeUnlock() { +Serial::SerialImpl::writeUnlock () +{ int result = pthread_mutex_unlock(&this->write_mutex); if (result) { THROW (IOException, result); diff --git a/src/impl/win.cc b/src/impl/win.cc new file mode 100644 index 0000000..9b1f6e5 --- /dev/null +++ b/src/impl/win.cc @@ -0,0 +1,549 @@ +/* Copyright 2012 William Woodall and John Harrison */ + +#include "serial/impl/win.h" + +using std::string; +using std::stringstream; +using std::invalid_argument; +using serial::Serial; +using serial::SerialExecption; +using serial::PortNotOpenedException; +using serial::IOException; + + +Serial::SerialImpl::SerialImpl (const string &port, unsigned long baudrate, + bytesize_t bytesize, + parity_t parity, stopbits_t stopbits, + flowcontrol_t flowcontrol) + : port_ (port), fd_ (INVALID_HANDLE_VALUE), is_open_ (false), + baudrate_ (baudrate), parity_ (parity), + bytesize_ (bytesize), stopbits_ (stopbits), flowcontrol_ (flowcontrol) +{ + read_mutex = CreateMutex(NULL, false, NULL); + write_mutex = CreateMutex(NULL, false, NULL); + if (port_.empty () == false) + open (); +} + +Serial::SerialImpl::~SerialImpl () +{ + this->close(); + CloseHandle(read_mutex); + CloseHandle(write_mutex); +} + +void +Serial::SerialImpl::open () +{ + if (port_.empty ()) { + throw invalid_argument ("Empty port is invalid."); + } + if (is_open_ == true) { + throw SerialExecption ("Serial port already open."); + } + + fd_ = CreateFile(port_, + GENERIC_READ | GENERIC_WRITE, + 0, + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + 0); + + if (fd_ == INVALID_HANDLE_VALUE) { + DWORD errno = GetLastError(); + switch (errno) { + case ERROR_FILE_NOT_FOUND: + stringstream ss; + ss << "Specified port, " << port_ << ", does not exist." + THROW (IOException, ss.str().c_str()); + default: + stringstream ss; + ss << "Unknown error opening the serial port: " << errno; + THROW (IOException, ss.str().c_str()); + } + } + + reconfigurePort(); + is_open_ = true; +} + +void +Serial::SerialImpl::reconfigurePort () +{ + if (fd_ == INVALID_HANDLE_VALUE) { + // Can only operate on a valid file descriptor + THROW (IOException, "Invalid file descriptor, is the serial port open?"); + } + + DCB dcbSerialParams = {0}; + + dcbSerial.DCBlength=sizeof(dcbSerialParams); + + if (!GetCommState(fd_, &dcbSerialParams)) { + //error getting state + THROW (IOException, "Error getting the serial port state."); + } + + // setup baud rate + switch (baudrate_) { +#ifdef CBR_0 + case 0: dcbSerialParams.BaudRate = CBR_0; break; +#endif +#ifdef CBR_50 + case 50: dcbSerialParams.BaudRate = CBR_50; break; +#endif +#ifdef CBR_75 + case 75: dcbSerialParams.BaudRate = CBR_75; break; +#endif +#ifdef CBR_110 + case 110: dcbSerialParams.BaudRate = CBR_110; break; +#endif +#ifdef CBR_134 + case 134: dcbSerialParams.BaudRate = CBR_134; break; +#endif +#ifdef CBR_150 + case 150: dcbSerialParams.BaudRate = CBR_150; break; +#endif +#ifdef CBR_200 + case 200: dcbSerialParams.BaudRate = CBR_200; break; +#endif +#ifdef CBR_300 + case 300: dcbSerialParams.BaudRate = CBR_300; break; +#endif +#ifdef CBR_600 + case 600: dcbSerialParams.BaudRate = CBR_600; break; +#endif +#ifdef CBR_1200 + case 1200: dcbSerialParams.BaudRate = CBR_1200; break; +#endif +#ifdef CBR_1800 + case 1800: dcbSerialParams.BaudRate = CBR_1800; break; +#endif +#ifdef CBR_2400 + case 2400: dcbSerialParams.BaudRate = CBR_2400; break; +#endif +#ifdef CBR_4800 + case 4800: dcbSerialParams.BaudRate = CBR_4800; break; +#endif +#ifdef CBR_7200 + case 7200: dcbSerialParams.BaudRate = CBR_7200; break; +#endif +#ifdef CBR_9600 + case 9600: dcbSerialParams.BaudRate = CBR_9600; break; +#endif +#ifdef CBR_14400 + case 14400: dcbSerialParams.BaudRate = CBR_14400; break; +#endif +#ifdef CBR_19200 + case 19200: dcbSerialParams.BaudRate = CBR_19200; break; +#endif +#ifdef CBR_28800 + case 28800: dcbSerialParams.BaudRate = CBR_28800; break; +#endif +#ifdef CBR_57600 + case 57600: dcbSerialParams.BaudRate = CBR_57600; break; +#endif +#ifdef CBR_76800 + case 76800: dcbSerialParams.BaudRate = CBR_76800; break; +#endif +#ifdef CBR_38400 + case 38400: dcbSerialParams.BaudRate = CBR_38400; break; +#endif +#ifdef CBR_115200 + case 115200: dcbSerialParams.BaudRate = CBR_115200; break; +#endif +#ifdef CBR_128000 + case 128000: dcbSerialParams.BaudRate = CBR_128000; break; +#endif +#ifdef CBR_153600 + case 153600: dcbSerialParams.BaudRate = CBR_153600; break; +#endif +#ifdef CBR_230400 + case 230400: dcbSerialParams.BaudRate = CBR_230400; break; +#endif +#ifdef CBR_256000 + case 256000: dcbSerialParams.BaudRate = CBR_256000; break; +#endif +#ifdef CBR_460800 + case 460800: dcbSerialParams.BaudRate = CBR_460800; break; +#endif +#ifdef CBR_921600 + case 921600: dcbSerialParams.BaudRate = CBR_921600; break; +#endif + default: + // Try to blindly assign it + dcbSerialParams.BaudRate = baudrate_; + } + + // setup char len + if (bytesize_ == eightbits) + dcbSerialParams.ByteSize = 8; + else if (bytesize_ == sevenbits) + dcbSerialParams.ByteSize = 7; + else if (bytesize_ == sixbits) + dcbSerialParams.ByteSize = 6; + else if (bytesize_ == fivebits) + dcbSerialParams.ByteSize = 5; + else + throw invalid_argument ("invalid char len"); + + // setup stopbits + if (stopbits_ == stopbits_one) + dcbSerialParams.StopBits = ONESTOPBIT; + else if (stopbits_ == stopbits_one_point_five) + dcbSerialParams.StopBits = ONE5STOPBITS; + else if (stopbits_ == stopbits_two) + dcbSerialParams.StopBits = TWOSTOPBITS; + else + throw invalid_argument ("invalid stop bit"); + + // setup parity + if (parity_ == parity_none) { + dcbSerialParams.Parity = NOPARITY; + } else if (parity_ == parity_even) { + dcbSerialParams.Parity = EVENPARITY; + } else if (parity_ == parity_odd) { + dcbSerialParams.Parity = ODDPARITY; + } else { + throw invalid_argument ("invalid parity"); + } + + // activate settings + if(!SetCommState(fd_, &dcbSerialParams)){ + THROW (IOException, "Error setting serial port settings."); + } +} + +void +Serial::SerialImpl::close () +{ + if (is_open_ == true) { + if (fd_ != INVALID_HANDLE_VALUE) { + CloseHandle(fd_); + fd_ = INVALID_HANDLE_VALUE; + } + is_open_ = false; + } +} + +bool +Serial::SerialImpl::isOpen () const +{ + return is_open_; +} + +size_t +Serial::SerialImpl::available () +{ + THROW (IOException, "available is not implemented on Windows."); +} + +size_t +Serial::SerialImpl::read (uint8_t *buf, size_t size) +{ + if (!is_open_) { + throw PortNotOpenedException ("Serial::read"); + } + DWORD bytes_read; + if (!ReadFile(fd_, buf, size, &bytes_read, NULL)) { + stringstream ss; + ss << "Error while reading from the serial port: " << GetLastError(); + THROW (IOException, ss.str().c_str()); + } + return reinterpret_cast (bytes_read); +} + +size_t +Serial::SerialImpl::write (const uint8_t *data, size_t length) +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::write"); + } + DWORD bytes_written; + if (!ReadFile(fd_, buf, size, &bytes_written, NULL)) { + stringstream ss; + ss << "Error while writing to the serial port: " << GetLastError(); + THROW (IOException, ss.str().c_str()); + } + return reinterpret_cast (bytes_written); +} + +void +Serial::SerialImpl::setPort (const string &port) +{ + port_ = port; +} + +string +Serial::SerialImpl::getPort () const +{ + return port_; +} + +void +Serial::SerialImpl::setTimeout (serial::Timeout &timeout) +{ + timeout_ = timeout; + COMMTIMEOUTS timeouts = {0}; + timeouts.ReadIntervalTimeout = timeout_.inter_byte_timeout; + timeouts.ReadTotalTimeoutConstant = timeout_.read_timeout_constant; + timeouts.ReadTotalTimeoutMultiplier = timeout_.read_timeout_multiplier; + timeouts.WriteTotalTimeoutConstant = timeout_.write_timeout_constant; + timeouts.WriteTotalTimeoutMultiplier = timeout_.write_timeout_multiplier; + if(!SetCommTimeouts(fd_, &timeouts)){ + THROW (IOException, "Error setting timeouts."); + } +} + +serial::Timeout +Serial::SerialImpl::getTimeout () const +{ + return timeout_; +} + +void +Serial::SerialImpl::setBaudrate (unsigned long baudrate) +{ + baudrate_ = baudrate; + if (is_open_) + reconfigurePort (); +} + +unsigned long +Serial::SerialImpl::getBaudrate () const +{ + return baudrate_; +} + +void +Serial::SerialImpl::setBytesize (serial::bytesize_t bytesize) +{ + bytesize_ = bytesize; + if (is_open_) + reconfigurePort (); +} + +serial::bytesize_t +Serial::SerialImpl::getBytesize () const +{ + return bytesize_; +} + +void +Serial::SerialImpl::setParity (serial::parity_t parity) +{ + parity_ = parity; + if (is_open_) + reconfigurePort (); +} + +serial::parity_t +Serial::SerialImpl::getParity () const +{ + return parity_; +} + +void +Serial::SerialImpl::setStopbits (serial::stopbits_t stopbits) +{ + stopbits_ = stopbits; + if (is_open_) + reconfigurePort (); +} + +serial::stopbits_t +Serial::SerialImpl::getStopbits () const +{ + return stopbits_; +} + +void +Serial::SerialImpl::setFlowcontrol (serial::flowcontrol_t flowcontrol) +{ + flowcontrol_ = flowcontrol; + if (is_open_) + reconfigurePort (); +} + +serial::flowcontrol_t +Serial::SerialImpl::getFlowcontrol () const +{ + return flowcontrol_; +} + +void +Serial::SerialImpl::flush () +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::flush"); + } + FlushFileBuffers (fd_); +} + +void +Serial::SerialImpl::flushInput () +{ + THROW (IOException, "flushInput is not supported on Windows."); +} + +void +Serial::SerialImpl::flushOutput () +{ + THROW (IOException, "flushOutput is not supported on Windows."); +} + +void +Serial::SerialImpl::sendBreak (int duration) +{ + THROW (IOException, "sendBreak is not supported on Windows."); +} + +void +Serial::SerialImpl::setBreak (bool level) +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::setBreak"); + } + if (level) { + EscapeCommFunction (fd_, SETBREAK); + } else { + EscapeCommFunction (fd_, CLRBREAK); + } +} + +void +Serial::SerialImpl::setRTS (bool level) +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::setRTS"); + } + if (level) { + EscapeCommFunction (fd_, SETRTS); + } else { + EscapeCommFunction (fd_, CLRRTS); + } +} + +void +Serial::SerialImpl::setDTR (bool level) +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::setDTR"); + } + if (level) { + EscapeCommFunction (fd_, SETDTR); + } else { + EscapeCommFunction (fd_, CLRDTR); + } +} + +bool +Serial::SerialImpl::waitForChange () +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::waitForChange"); + } + DWORD dwCommEvent; + + if (!SetCommMask(fd_, EV_CTS | EV_DSR | EV_RING | EV_RLSD)) { + // Error setting communications mask + return false; + } + + if (!WaitCommEvent(fd_, &dwCommEvent, NULL)) { + // An error occurred waiting for the event. + return false; + } else { + // Event has occurred. + return true; + } +} + +bool +Serial::SerialImpl::getCTS () +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::getCTS"); + } + DWORD dwModemStatus; + if (!GetCommModemStatus(fd_, &dwModemStatus)) + // Error in GetCommModemStatus; + THROW (IOException, "Error getting the status of the CTS line."); + + return MS_CTS_ON & dwModemStatus; +} + +bool +Serial::SerialImpl::getDSR () +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::getDSR"); + } + DWORD dwModemStatus; + if (!GetCommModemStatus(fd_, &dwModemStatus)) + // Error in GetCommModemStatus; + THROW (IOException, "Error getting the status of the DSR line."); + + return MS_DSR_ON & dwModemStatus; +} + +bool +Serial::SerialImpl::getRI() +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::getRI"); + } + DWORD dwModemStatus; + if (!GetCommModemStatus(fd_, &dwModemStatus)) + // Error in GetCommModemStatus; + THROW (IOException, "Error getting the status of the DSR line."); + + return MS_RING_ON & dwModemStatus; +} + +bool +Serial::SerialImpl::getCD() +{ + if (is_open_ == false) { + throw PortNotOpenedException ("Serial::getCD"); + } + DWORD dwModemStatus; + if (!GetCommModemStatus(fd_, &dwModemStatus)) + // Error in GetCommModemStatus; + THROW (IOException, "Error getting the status of the DSR line."); + + return MS_RLSD_ON & dwModemStatus; +} + +void +Serial::SerialImpl::readLock() +{ + if (WaitForSingleObject(read_mutex, INFINITE) != WAIT_OBJECT_0) { + THROW (IOException, "Error claiming read mutex."); + } +} + +void +Serial::SerialImpl::readUnlock() +{ + if (!ReleaseMutex(read_mutex)) { + THROW (IOException, "Error releasing read mutex."); + } +} + +void +Serial::SerialImpl::writeLock() +{ + if (WaitForSingleObject(write_mutex, INFINITE) != WAIT_OBJECT_0) { + THROW (IOException, "Error claiming write mutex."); + } +} + +void +Serial::SerialImpl::writeUnlock() +{ + if (!ReleaseMutex(write_mutex)) { + THROW (IOException, "Error releasing write mutex."); + } +} diff --git a/src/impl/windows.cc b/src/impl/windows.cc deleted file mode 100644 index 5e82227..0000000 --- a/src/impl/windows.cc +++ /dev/null @@ -1,670 +0,0 @@ -/* Copyright 2012 William Woodall and John Harrison */ - -#include "serial/impl/windows.h" - -using std::string; -using std::invalid_argument; -using serial::Serial; -using serial::SerialExecption; -using serial::PortNotOpenedException; -using serial::IOException; - - -Serial::SerialImpl::SerialImpl (const string &port, unsigned long baudrate, - long timeout, bytesize_t bytesize, - parity_t parity, stopbits_t stopbits, - flowcontrol_t flowcontrol) -: port_ (port), fd_ (-1), isOpen_ (false), xonxoff_ (true), rtscts_ (false), - timeout_ (timeout), baudrate_ (baudrate), parity_ (parity), bytesize_ (bytesize), - stopbits_ (stopbits), flowcontrol_ (flowcontrol) -{ - if (port_.empty () == false) - open (); -} - -Serial::SerialImpl::~SerialImpl () -{ - close(); -} - -void -Serial::SerialImpl::open () -{ - if (port_.empty()) - { - throw invalid_argument ("bad port specified"); - } - if (isOpen_ == true) - { - throw SerialExecption ("port already open"); - } - - fd_ = ::open (port_.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); - - if (fd_ == -1) - { - switch (errno) - { - case EINTR: - // Recurse because this is a recoverable error. - open (); - return; - case ENFILE: - case EMFILE: - throw IOException ("to many file handles open"); - break; - default: - throw IOException (errno); - } - } - - reconfigurePort(); - isOpen_ = true; -} - -void -Serial::SerialImpl::reconfigurePort () -{ - if (fd_ == -1) - { - // Can only operate on a valid file descriptor - throw IOException ("invalid file descriptor"); - } - - struct termios options; // The options for the file descriptor - - if (tcgetattr(fd_, &options) == -1) - { - throw IOException ("::tcgetattr"); - } - - // set up raw mode / no echo / binary - options.c_cflag |= (unsigned long) (CLOCAL|CREAD); - options.c_lflag &= (unsigned long) ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL| - ISIG|IEXTEN); //|ECHOPRT - - options.c_oflag &= (unsigned long) ~(OPOST); - options.c_iflag &= (unsigned long) ~(INLCR|IGNCR|ICRNL|IGNBRK); -#ifdef IUCLC - options.c_iflag &= (unsigned long) ~IUCLC; -#endif -#ifdef PARMRK - options.c_iflag &= (unsigned long) ~PARMRK; -#endif - - // setup baud rate - bool custom_baud = false; - speed_t baud; - switch (baudrate_) - { -#ifdef B0 - case 0: baud = B0; break; -#endif -#ifdef B50 - case 50: baud = B50; break; -#endif -#ifdef B75 - case 75: baud = B75; break; -#endif -#ifdef B110 - case 110: baud = B110; break; -#endif -#ifdef B134 - case 134: baud = B134; break; -#endif -#ifdef B150 - case 150: baud = B150; break; -#endif -#ifdef B200 - case 200: baud = B200; break; -#endif -#ifdef B300 - case 300: baud = B300; break; -#endif -#ifdef B600 - case 600: baud = B600; break; -#endif -#ifdef B1200 - case 1200: baud = B1200; break; -#endif -#ifdef B1800 - case 1800: baud = B1800; break; -#endif -#ifdef B2400 - case 2400: baud = B2400; break; -#endif -#ifdef B4800 - case 4800: baud = B4800; break; -#endif -#ifdef B7200 - case 7200: baud = B7200; break; -#endif -#ifdef B9600 - case 9600: baud = B9600; break; -#endif -#ifdef B14400 - case 14400: baud = B14400; break; -#endif -#ifdef B19200 - case 19200: baud = B19200; break; -#endif -#ifdef B28800 - case 28800: baud = B28800; break; -#endif -#ifdef B57600 - case 57600: baud = B57600; break; -#endif -#ifdef B76800 - case 76800: baud = B76800; break; -#endif -#ifdef B38400 - case 38400: baud = B38400; break; -#endif -#ifdef B115200 - case 115200: baud = B115200; break; -#endif -#ifdef B128000 - case 128000: baud = B128000; break; -#endif -#ifdef B153600 - case 153600: baud = B153600; break; -#endif -#ifdef B230400 - case 230400: baud = B230400; break; -#endif -#ifdef B256000 - case 256000: baud = B256000; break; -#endif -#ifdef B460800 - case 460800: baud = B460800; break; -#endif -#ifdef B921600 - case 921600: baud = B921600; break; -#endif - default: - custom_baud = true; -// Mac OS X 10.x Support -#if defined(__APPLE__) && defined(__MACH__) -#define IOSSIOSPEED _IOW('T', 2, speed_t) - int new_baud = static_cast (baudrate_); - if (ioctl (fd_, IOSSIOSPEED, &new_baud, 1) < 0) - { - throw IOException (errno); - } -// Linux Support -#elif defined(__linux__) - struct serial_struct ser; - ioctl(fd_, TIOCGSERIAL, &ser); - // set custom divisor - ser.custom_divisor = ser.baud_base / baudrate_; - // update flags - ser.flags &= ~ASYNC_SPD_MASK; - ser.flags |= ASYNC_SPD_CUST; - - if (ioctl(fd_, TIOCSSERIAL, ser) < 0) - { - throw IOException (errno); - } -#else - throw invalid_argument ("OS does not currently support custom bauds"); -#endif - } - if (custom_baud == false) - { -#ifdef _BSD_SOURCE - ::cfsetspeed(&options, baud); -#else - ::cfsetispeed(&options, baud); - ::cfsetospeed(&options, baud); -#endif - } - - // setup char len - options.c_cflag &= (unsigned long) ~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 invalid_argument ("invalid char len"); - // setup stopbits - if (stopbits_ == STOPBITS_ONE) - options.c_cflag &= (unsigned long) ~(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 invalid_argument ("invalid stop bit"); - // setup parity - options.c_iflag &= (unsigned long) ~(INPCK|ISTRIP); - if (parity_ == PARITY_NONE) - { - options.c_cflag &= (unsigned long) ~(PARENB|PARODD); - } - else if (parity_ == PARITY_EVEN) - { - options.c_cflag &= (unsigned long) ~(PARODD); - options.c_cflag |= (PARENB); - } - else if (parity_ == PARITY_ODD) - { - options.c_cflag |= (PARENB|PARODD); - } - else - { - throw invalid_argument ("invalid parity"); - } - // setup flow control - // xonxoff -#ifdef IXANY - if (xonxoff_) - options.c_iflag |= (IXON|IXOFF); //|IXANY) - else - options.c_iflag &= (unsigned long) ~(IXON|IXOFF|IXANY); -#else - if (xonxoff_) - options.c_iflag |= (IXON|IXOFF); - else - options.c_iflag &= (unsigned long) ~(IXON|IXOFF); -#endif - // rtscts -#ifdef CRTSCTS - if (rtscts_) - options.c_cflag |= (CRTSCTS); - else - options.c_cflag &= (unsigned long) ~(CRTSCTS); -#elif defined CNEW_RTSCTS - if (rtscts_) - options.c_cflag |= (CNEW_RTSCTS); - else - options.c_cflag &= (unsigned long) ~(CNEW_RTSCTS); -#else -#error "OS Support seems wrong." -#endif - - options.c_cc[VMIN] = 1; // Minimum of 1 character in the buffer - options.c_cc[VTIME] = 0; // timeout on waiting for new data - - // activate settings - ::tcsetattr (fd_, TCSANOW, &options); -} - -void -Serial::SerialImpl::close () -{ - if (isOpen_ == true) - { - if (fd_ != -1) - { - ::close (fd_); // Ignoring the outcome - fd_ = -1; - } - isOpen_ = false; - } -} - -bool -Serial::SerialImpl::isOpen () const -{ - return isOpen_; -} - -size_t -Serial::SerialImpl::available () -{ - if (!isOpen_) - { - return 0; - } - int count = 0; - int result = ioctl (fd_, TIOCINQ, &count); - if (result == 0) - { - return static_cast (count); - } - else - { - throw IOException (errno); - } -} - -size_t -Serial::SerialImpl::read (char* buf, size_t size) -{ - if (!isOpen_) - { - throw PortNotOpenedException ("Serial::read"); - } - fd_set readfds; - ssize_t bytes_read = 0; - int count = 0; - while (true) - { - count++; - if (timeout_ != -1) - { - FD_ZERO (&readfds); - FD_SET (fd_, &readfds); - struct timeval timeout; - timeout.tv_sec = timeout_ / 1000; - timeout.tv_usec = static_cast (timeout_ % 1000) * 1000; - int r = select (fd_ + 1, &readfds, NULL, NULL, &timeout); - - if (r == -1 && errno == EINTR) - continue; - - if (r == -1) - { - throw IOException (errno); - } - } - - if (timeout_ == -1 || FD_ISSET (fd_, &readfds)) - { - bytes_read = ::read (fd_, buf, size); - // 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 SerialExecption ("device reports readiness to read but " - "returned no data (device disconnected?)"); - } - break; - } - else - { - break; - } - } - return static_cast (bytes_read); -} - -size_t -Serial::SerialImpl::write (const string &data) -{ - if (isOpen_ == false) - { - throw PortNotOpenedException ("Serial::write"); - } - - fd_set writefds; - ssize_t bytes_written = 0; - while (true) - { - if (timeout_ != -1) - { - FD_ZERO (&writefds); - FD_SET (fd_, &writefds); - struct timeval timeout; - timeout.tv_sec = timeout_ / 1000; - timeout.tv_usec = static_cast (timeout_ % 1000) * 1000; - int r = select (fd_ + 1, NULL, &writefds, NULL, &timeout); - - if (r == -1 && errno == EINTR) - continue; - - if (r == -1) - { - throw IOException (errno); - } - } - - if (timeout_ == -1 || FD_ISSET (fd_, &writefds)) - { - bytes_written = ::write (fd_, data.c_str (), data.length ()); - // read should always return some data as select reported it was - // ready to read when we get to this point. - if (bytes_written < 1) - { - // Disconnected devices, at least on Linux, show the - // behavior that they are always ready to read immediately - // but reading returns nothing. - throw SerialExecption ("device reports readiness to read but " - "returned no data (device disconnected?)"); - } - break; - } - else - { - break; - } - } - - return static_cast (bytes_written); -} - -void -Serial::SerialImpl::setPort (const string &port) -{ - port_ = port; -} - -string -Serial::SerialImpl::getPort () const -{ - return port_; -} - -void -Serial::SerialImpl::setTimeout (long timeout) -{ - timeout_ = timeout; -} - -long -Serial::SerialImpl::getTimeout () const -{ - return timeout_; -} - -void -Serial::SerialImpl::setBaudrate (unsigned long baudrate) -{ - baudrate_ = baudrate; - if (isOpen_) - reconfigurePort (); -} - -unsigned long -Serial::SerialImpl::getBaudrate () const -{ - return baudrate_; -} - -void -Serial::SerialImpl::setBytesize (serial::bytesize_t bytesize) -{ - bytesize_ = bytesize; - if (isOpen_) - reconfigurePort (); -} - -serial::bytesize_t -Serial::SerialImpl::getBytesize () const -{ - return bytesize_; -} - -void -Serial::SerialImpl::setParity (serial::parity_t parity) -{ - parity_ = parity; - if (isOpen_) - reconfigurePort (); -} - -serial::parity_t -Serial::SerialImpl::getParity () const -{ - return parity_; -} - -void -Serial::SerialImpl::setStopbits (serial::stopbits_t stopbits) -{ - stopbits_ = stopbits; - if (isOpen_) - reconfigurePort (); -} - -serial::stopbits_t -Serial::SerialImpl::getStopbits () const -{ - return stopbits_; -} - -void -Serial::SerialImpl::setFlowcontrol (serial::flowcontrol_t flowcontrol) -{ - flowcontrol_ = flowcontrol; - if (isOpen_) - reconfigurePort (); -} - -serial::flowcontrol_t -Serial::SerialImpl::getFlowcontrol () const -{ - return flowcontrol_; -} - -void -Serial::SerialImpl::flush () -{ - if (isOpen_ == false) - { - throw PortNotOpenedException ("Serial::flush"); - } - tcdrain (fd_); -} - -void -Serial::SerialImpl::flushInput () -{ - if (isOpen_ == false) - { - throw PortNotOpenedException ("Serial::flushInput"); - } - tcflush (fd_, TCIFLUSH); -} - -void -Serial::SerialImpl::flushOutput () -{ - if (isOpen_ == false) - { - throw PortNotOpenedException ("Serial::flushOutput"); - } - tcflush (fd_, TCOFLUSH); -} - -void -Serial::SerialImpl::sendBreak (int duration) -{ - if (isOpen_ == false) - { - throw PortNotOpenedException ("Serial::sendBreak"); - } - tcsendbreak (fd_, static_cast (duration/4)); -} - -void -Serial::SerialImpl::setBreak (bool level) -{ - if (isOpen_ == false) - { - throw PortNotOpenedException ("Serial::setBreak"); - } - if (level) - { - ioctl (fd_, TIOCSBRK); - } - else { - ioctl (fd_, TIOCCBRK); - } -} - -void -Serial::SerialImpl::setRTS (bool level) -{ - if (isOpen_ == false) - { - throw PortNotOpenedException ("Serial::setRTS"); - } - if (level) - { - ioctl (fd_, TIOCMBIS, TIOCM_RTS); - } - else { - ioctl (fd_, TIOCMBIC, TIOCM_RTS); - } -} - -void -Serial::SerialImpl::setDTR (bool level) -{ - if (isOpen_ == false) - { - throw PortNotOpenedException ("Serial::setDTR"); - } - if (level) - { - ioctl (fd_, TIOCMBIS, TIOCM_DTR); - } - else - { - ioctl (fd_, TIOCMBIC, TIOCM_DTR); - } -} - -bool -Serial::SerialImpl::getCTS () -{ - if (isOpen_ == false) - { - throw PortNotOpenedException ("Serial::getCTS"); - } - int s = ioctl (fd_, TIOCMGET, 0); - return (s & TIOCM_CTS) != 0; -} - -bool -Serial::SerialImpl::getDSR() -{ - if (isOpen_ == false) - { - throw PortNotOpenedException ("Serial::getDSR"); - } - int s = ioctl(fd_, TIOCMGET, 0); - return (s & TIOCM_DSR) != 0; -} - -bool -Serial::SerialImpl::getRI() -{ - if (isOpen_ == false) - { - throw PortNotOpenedException ("Serial::getRI"); - } - int s = ioctl (fd_, TIOCMGET, 0); - return (s & TIOCM_RI) != 0; -} - -bool -Serial::SerialImpl::getCD() -{ - if (isOpen_ == false) - { - throw PortNotOpenedException ("Serial::getCD"); - } - int s = ioctl (fd_, TIOCMGET, 0); - return (s & TIOCM_CD) != 0; -} - diff --git a/src/serial.cc b/src/serial.cc index 2133b06..dc27d01 100644 --- a/src/serial.cc +++ b/src/serial.cc @@ -33,6 +33,11 @@ public: this->pimpl_->readUnlock(); } private: + // Disable copy constructors + ScopedReadLock(const ScopedReadLock&); + void operator=(const ScopedReadLock&); + const ScopedReadLock& operator=(ScopedReadLock); + SerialImpl *pimpl_; }; @@ -45,16 +50,20 @@ public: this->pimpl_->writeUnlock(); } private: + // Disable copy constructors + ScopedWriteLock(const ScopedWriteLock&); + void operator=(const ScopedWriteLock&); + const ScopedWriteLock& operator=(ScopedWriteLock); SerialImpl *pimpl_; }; -Serial::Serial (const string &port, unsigned long baudrate, long timeout, +Serial::Serial (const string &port, uint32_t baudrate, Timeout timeout, bytesize_t bytesize, parity_t parity, stopbits_t stopbits, flowcontrol_t flowcontrol) - : read_cache_("") + : read_cache_(""), pimpl_(new SerialImpl (port, baudrate, bytesize, parity, + stopbits, flowcontrol)) { - pimpl_ = new SerialImpl (port, baudrate, timeout, bytesize, parity, - stopbits, flowcontrol); + pimpl_->setTimeout(timeout); } Serial::~Serial () @@ -87,23 +96,23 @@ Serial::available () } size_t -Serial::read_ (unsigned char *buffer, size_t size) +Serial::read_ (uint8_t *buffer, size_t size) { return this->pimpl_->read (buffer, size); } size_t -Serial::read (unsigned char *buffer, size_t size) +Serial::read (uint8_t *buffer, size_t size) { ScopedReadLock (this->pimpl_); return this->pimpl_->read (buffer, size); } size_t -Serial::read (std::vector &buffer, size_t size) +Serial::read (std::vector &buffer, size_t size) { ScopedReadLock (this->pimpl_); - unsigned char *buffer_ = new unsigned char[size]; + uint8_t *buffer_ = new uint8_t[size]; size_t bytes_read = this->pimpl_->read (buffer_, size); buffer.insert (buffer.end (), buffer_, buffer_+bytes_read); delete[] buffer_; @@ -114,7 +123,7 @@ size_t Serial::read (std::string &buffer, size_t size) { ScopedReadLock (this->pimpl_); - unsigned char *buffer_ = new unsigned char[size]; + uint8_t *buffer_ = new uint8_t[size]; size_t bytes_read = this->pimpl_->read (buffer_, size); buffer.append (reinterpret_cast(buffer_), bytes_read); delete[] buffer_; @@ -134,8 +143,8 @@ Serial::readline (string &buffer, size_t size, string eol) { ScopedReadLock (this->pimpl_); size_t eol_len = eol.length (); - unsigned char *buffer_ = static_cast - (alloca (size * sizeof (unsigned char))); + uint8_t *buffer_ = static_cast + (alloca (size * sizeof (uint8_t))); size_t read_so_far = 0; while (true) { @@ -170,8 +179,8 @@ Serial::readlines (size_t size, string eol) ScopedReadLock (this->pimpl_); std::vector lines; size_t eol_len = eol.length (); - unsigned char *buffer_ = static_cast - (alloca (size * sizeof (unsigned char))); + uint8_t *buffer_ = static_cast + (alloca (size * sizeof (uint8_t))); size_t read_so_far = 0; size_t start_of_line = 0; while (read_so_far < size) { @@ -209,7 +218,28 @@ size_t Serial::write (const string &data) { ScopedWriteLock(this->pimpl_); - return pimpl_->write (data); + return this->write_ (reinterpret_cast(data.c_str()), + data.length()); +} + +size_t +Serial::write (const std::vector &data) +{ + ScopedWriteLock(this->pimpl_); + return this->write_ (&data[0], data.size()); +} + +size_t +Serial::write (const uint8_t *data, size_t size) +{ + ScopedWriteLock(this->pimpl_); + return this->write_(data, size); +} + +size_t +Serial::write_ (const uint8_t *data, size_t length) +{ + return pimpl_->write (data, length); } void @@ -230,26 +260,26 @@ Serial::getPort () const } void -Serial::setTimeout (long timeout) +Serial::setTimeout (serial::Timeout &timeout) { pimpl_->setTimeout (timeout); } -long +serial::Timeout Serial::getTimeout () const { return pimpl_->getTimeout (); } void -Serial::setBaudrate (unsigned long baudrate) +Serial::setBaudrate (uint32_t baudrate) { pimpl_->setBaudrate (baudrate); } -unsigned long +uint32_t Serial::getBaudrate () const { - return pimpl_->getBaudrate (); + return uint32_t(pimpl_->getBaudrate ()); } void @@ -341,6 +371,11 @@ void Serial::setDTR (bool level) pimpl_->setDTR (level); } +bool Serial::waitForChange() +{ + return pimpl_->waitForChange(); +} + bool Serial::getCTS () { return pimpl_->getCTS ();