From 28025034bd54582ad2879357ac01273dc1c9a222 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Wed, 6 Nov 2013 21:53:08 -0500 Subject: [PATCH 1/4] Add waitReadable and waitByteTimes stubs. --- include/serial/impl/unix.h | 6 ++ include/serial/serial.h | 14 ++++ src/impl/unix.cc | 128 ++++++++++++++++++++----------------- src/serial.cc | 13 ++++ 4 files changed, 104 insertions(+), 57 deletions(-) diff --git a/include/serial/impl/unix.h b/include/serial/impl/unix.h index df73e2d..e5fc0d0 100644 --- a/include/serial/impl/unix.h +++ b/include/serial/impl/unix.h @@ -86,6 +86,12 @@ public: size_t available (); + bool + waitReadable (uint32_t timeout); + + void + waitByteTimes (size_t count); + size_t read (uint8_t *buf, size_t size = 1); diff --git a/include/serial/serial.h b/include/serial/serial.h index bf664c9..70d1d6a 100644 --- a/include/serial/serial.h +++ b/include/serial/serial.h @@ -217,6 +217,20 @@ public: size_t available (); + /*! Block until there is serial data to read or read_timeout_constant + * number of milliseconds have elapsed. The return value is true when + * the function exits with the port in a readable state, false otherwise + * (due to timeout or select interruption). */ + bool + waitReadable (); + + /*! Block for a period of time corresponding to the transmission time of + * count characters at present serial settings. This may be used in con- + * junction with waitReadable to read larger blocks of data from the + * port. */ + void + waitByteTimes (size_t count); + /*! Read a given amount of bytes from the serial port into a given buffer. * * The read function will return in one of three cases: diff --git a/src/impl/unix.cc b/src/impl/unix.cc index 561db21..941dcd1 100755 --- a/src/impl/unix.cc +++ b/src/impl/unix.cc @@ -454,6 +454,47 @@ Serial::SerialImpl::available () } } +bool +Serial::SerialImpl::waitReadable (uint32_t timeout) +{ + fd_set readfds; + FD_ZERO (&readfds); + FD_SET (fd_, &readfds); + + // Call select to block for serial data or a timeout + timespec timeout_ts (timespec_from_ms (timeout)); + int r = pselect (fd_ + 1, &readfds, NULL, NULL, &timeout_ts, NULL); + + if (r < 0) { + // Select was interrupted + if (errno == EINTR) { + return false; + } + // Otherwise there was some error + THROW (IOException, errno); + } + + // Timeout occurred + if (r == 0) { + return false; + } + + // This shouldn't happen, if r > 0 our fd has to be in the list! + if (!FD_ISSET (fd_, &readfds)) { + THROW (IOException, "select reports ready to read, but our fd isn't" + " in the list, this shouldn't happen!"); + } + + // Data available to read. + return true; +} + +void +Serial::SerialImpl::waitByteTimes (size_t count) +{ + +} + size_t Serial::SerialImpl::read (uint8_t *buf, size_t size) { @@ -461,7 +502,6 @@ Serial::SerialImpl::read (uint8_t *buf, size_t size) if (!is_open_) { throw PortNotOpenedException ("Serial::read"); } - fd_set readfds; size_t bytes_read = 0; // Calculate total timeout in milliseconds t_c + (t_m * N) @@ -486,66 +526,40 @@ Serial::SerialImpl::read (uint8_t *buf, size_t size) // Timeout for the next select is whichever is less of the remaining // total read timeout and the inter-byte timeout. - timespec timeout(timespec_from_ms(std::min(static_cast (timeout_remaining_ms), - timeout_.inter_byte_timeout))); + uint32_t timeout = std::min(static_cast (timeout_remaining_ms), + timeout_.inter_byte_timeout); - FD_ZERO (&readfds); - FD_SET (fd_, &readfds); - - // Call select to block for serial data or a timeout - int r = pselect (fd_ + 1, &readfds, NULL, NULL, &timeout, NULL); - - // Figure out what happened by looking at select's response 'r' - /** Error **/ - if (r < 0) { - // Select was interrupted, try again - if (errno == EINTR) { + // Wait for the device to be readable, and then attempt to read. + if (waitReadable(timeout)) { + // 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 should always return some data as select reported it was + // ready to read when we get to this point. + 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. + throw SerialException ("device reports readiness to read but " + "returned no data (device disconnected?)"); + } + // Update bytes_read + bytes_read += static_cast (bytes_read_now); + // If bytes_read == size then we have read everything we need + if (bytes_read == size) { + break; + } + // If bytes_read < size then we have more to read + if (bytes_read < size) { 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_, &readfds)) { - // 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 should always return some data as select reported it was - // ready to read when we get to this point. - 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. - throw SerialException ("device reports readiness to read but " - "returned no data (device disconnected?)"); - } - // Update bytes_read - bytes_read += static_cast (bytes_read_now); - // If bytes_read == size then we have read everything we need - if (bytes_read == size) { - break; - } - // If bytes_read < size then we have more to read - if (bytes_read < size) { - continue; - } - // If bytes_read > size then we have over read, which shouldn't happen - if (bytes_read > size) { - throw SerialException ("read over read, too many bytes where " - "read, this shouldn't happen, might be " - "a logical error!"); - } + // If bytes_read > size then we have over read, which shouldn't happen + if (bytes_read > size) { + throw SerialException ("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_read; diff --git a/src/serial.cc b/src/serial.cc index a247841..202fc9e 100755 --- a/src/serial.cc +++ b/src/serial.cc @@ -95,6 +95,19 @@ Serial::available () return pimpl_->available (); } +bool +Serial::waitReadable () +{ + serial::Timeout timeout(pimpl_->getTimeout ()); + return pimpl_->waitReadable(timeout.read_timeout_constant); +} + +void +Serial::waitByteTimes (size_t count) +{ + pimpl_->waitByteTimes(count); +} + size_t Serial::read_ (uint8_t *buffer, size_t size) { From d74c74bf15bb6179fc44a315c857bbf4e46018c5 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Wed, 6 Nov 2013 22:27:17 -0500 Subject: [PATCH 2/4] Fill out waitByteTimes, integrate it into read as discussed in ticket #37 --- include/serial/impl/unix.h | 3 ++- src/impl/unix.cc | 30 ++++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/include/serial/impl/unix.h b/include/serial/impl/unix.h index e5fc0d0..0fb38f2 100644 --- a/include/serial/impl/unix.h +++ b/include/serial/impl/unix.h @@ -199,8 +199,9 @@ private: bool xonxoff_; bool rtscts_; - Timeout timeout_; // Timeout for read operations + Timeout timeout_; // Timeout for read operations unsigned long baudrate_; // Baudrate + uint32_t byte_time_ns_; // Nanoseconds to transmit/receive a single byte parity_t parity_; // Parity bytesize_t bytesize_; // Size of the bytes diff --git a/src/impl/unix.cc b/src/impl/unix.cc index 941dcd1..610e47c 100755 --- a/src/impl/unix.cc +++ b/src/impl/unix.cc @@ -420,6 +420,16 @@ Serial::SerialImpl::reconfigurePort () // activate settings ::tcsetattr (fd_, TCSANOW, &options); + + // Update byte_time_ based on the new settings. + uint32_t bit_time_ns = 1e9 / baudrate_; + byte_time_ns_ = bit_time_ns * (1 + bytesize_ + parity_ + stopbits_); + + // Compensate for the stopbits_one_point_five enum being equal to int 3, + // and not 1.5. + if (stopbits_ == stopbits_one_point_five) { + byte_time_ns_ += ((1.5 - stopbits_one_point_five) * bit_time_ns); + } } void @@ -457,11 +467,10 @@ Serial::SerialImpl::available () bool Serial::SerialImpl::waitReadable (uint32_t timeout) { + // Setup a select call to block for serial data or a timeout fd_set readfds; FD_ZERO (&readfds); FD_SET (fd_, &readfds); - - // Call select to block for serial data or a timeout timespec timeout_ts (timespec_from_ms (timeout)); int r = pselect (fd_ + 1, &readfds, NULL, NULL, &timeout_ts, NULL); @@ -473,18 +482,15 @@ Serial::SerialImpl::waitReadable (uint32_t timeout) // Otherwise there was some error THROW (IOException, errno); } - // Timeout occurred if (r == 0) { return false; } - // This shouldn't happen, if r > 0 our fd has to be in the list! if (!FD_ISSET (fd_, &readfds)) { THROW (IOException, "select reports ready to read, but our fd isn't" " in the list, this shouldn't happen!"); } - // Data available to read. return true; } @@ -492,7 +498,8 @@ Serial::SerialImpl::waitReadable (uint32_t timeout) void Serial::SerialImpl::waitByteTimes (size_t count) { - + timespec wait_time = { 0, byte_time_ns_ * count }; + pselect (0, NULL, NULL, NULL, &wait_time, NULL); } size_t @@ -523,14 +530,21 @@ Serial::SerialImpl::read (uint8_t *buf, size_t size) // Timed out break; } - // Timeout for the next select is whichever is less of the remaining // total read timeout and the inter-byte timeout. uint32_t timeout = std::min(static_cast (timeout_remaining_ms), timeout_.inter_byte_timeout); - // Wait for the device to be readable, and then attempt to read. if (waitReadable(timeout)) { + // If it's a fixed-length multi-byte read, insert a wait here so that + // we can attempt to grab the whole thing in a single IO call. Skip + // this wait if a non-max inter_byte_timeout is specified. + if (size > 1 && timeout_.inter_byte_timeout == Timeout::max()) { + size_t bytes_available = available(); + if (bytes_available + bytes_read < size) { + waitByteTimes(size - (bytes_available + bytes_read)); + } + } // This should be non-blocking returning only what is available now // Then returning so that select can block again. ssize_t bytes_read_now = From 5ec56d8294bf162ba0da3ecfa7ce90467a1587e2 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Mon, 17 Feb 2014 22:50:16 -0500 Subject: [PATCH 3/4] Stubs for waitReadable and waitByteTimes on Windows. --- src/impl/win.cc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/impl/win.cc b/src/impl/win.cc index b3b4c0e..a59a4d0 100644 --- a/src/impl/win.cc +++ b/src/impl/win.cc @@ -288,6 +288,19 @@ Serial::SerialImpl::available () return static_cast(cs.cbInQue); } +bool +Serial::SerialImpl::waitReadable (uint32_t timeout) +{ + THROW (IOException, "waitReadable is not implemented on Windows."); + return false; +} + +void +Serial::SerialImpl::waitByteTimes (size_t count) +{ + THROW (IOException, "waitByteTimes is not implemented on Windows."); +} + size_t Serial::SerialImpl::read (uint8_t *buf, size_t size) { From 2906a6fe900aa91de4fb9aa6cec59a6c0ef2b3bb Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Mon, 28 Apr 2014 07:30:40 -0400 Subject: [PATCH 4/4] Add missing stubs to serial/impl/win.h header --- include/serial/impl/win.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/serial/impl/win.h b/include/serial/impl/win.h index 605f5d0..2c0c6cd 100644 --- a/include/serial/impl/win.h +++ b/include/serial/impl/win.h @@ -74,6 +74,12 @@ public: size_t available (); + + bool + waitReadable (uint32_t timeout); + + void + waitByteTimes (size_t count); size_t read (uint8_t *buf, size_t size = 1);