1
0
mirror of https://github.com/wjwwood/serial.git synced 2026-01-22 19:54:57 +08:00

Merge pull request #45 from clearpathrobotics/timespec-refactor2

Timespec refactor (again)
This commit is contained in:
William Woodall 2013-11-20 13:24:30 -08:00
commit f051c0a613
7 changed files with 178 additions and 115 deletions

View File

@ -6,7 +6,7 @@ install:
- sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu precise main" > /etc/apt/sources.list.d/ros-latest.list' - sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu precise main" > /etc/apt/sources.list.d/ros-latest.list'
- wget http://packages.ros.org/ros.key -O - | sudo apt-key add - - wget http://packages.ros.org/ros.key -O - | sudo apt-key add -
- sudo apt-get update - sudo apt-get update
- sudo apt-get install ros-groovy-catkin - sudo apt-get install ros-groovy-catkin libboost-dev
- source /opt/ros/groovy/setup.bash - source /opt/ros/groovy/setup.bash
script: script:
- make - make && make test

View File

@ -59,9 +59,5 @@ install(FILES include/serial/serial.h include/serial/v8stdint.h
## Tests ## Tests
if(CATKIN_ENABLE_TESTING) if(CATKIN_ENABLE_TESTING)
catkin_add_gtest(${PROJECT_NAME}-test tests/serial_tests.cc) add_subdirectory(tests)
target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME} ${Boost_LIBRARIES})
if(UNIX AND NOT APPLE)
target_link_libraries(${PROJECT_NAME}-test util)
endif()
endif() endif()

View File

@ -53,6 +53,16 @@ using std::invalid_argument;
using serial::SerialException; using serial::SerialException;
using serial::IOException; using serial::IOException;
class MillisecondTimer {
public:
MillisecondTimer(const uint32_t millis);
int64_t remaining();
private:
static timespec timespec_now();
timespec expiry;
};
class serial::Serial::SerialImpl { class serial::Serial::SerialImpl {
public: public:
SerialImpl (const string &port, SerialImpl (const string &port,

View File

@ -48,12 +48,62 @@
using std::string; using std::string;
using std::stringstream; using std::stringstream;
using std::invalid_argument; using std::invalid_argument;
using serial::MillisecondTimer;
using serial::Serial; using serial::Serial;
using serial::SerialException; using serial::SerialException;
using serial::PortNotOpenedException; using serial::PortNotOpenedException;
using serial::IOException; using serial::IOException;
MillisecondTimer::MillisecondTimer (const uint32_t millis)
: expiry(timespec_now())
{
int64_t tv_nsec = expiry.tv_nsec + (millis * 1e6);
if (tv_nsec >= 1e9) {
int64_t sec_diff = tv_nsec / static_cast<int> (1e9);
expiry.tv_nsec = tv_nsec - static_cast<int> (1e9 * sec_diff);
expiry.tv_sec += sec_diff;
} else {
expiry.tv_nsec = tv_nsec;
}
}
int64_t
MillisecondTimer::remaining ()
{
timespec now(timespec_now());
int64_t millis = (expiry.tv_sec - now.tv_sec) * 1e3;
millis += (expiry.tv_nsec - now.tv_nsec) / 1e6;
return millis;
}
timespec
MillisecondTimer::timespec_now ()
{
timespec time;
# ifdef __MACH__ // OS X does not have clock_gettime, use clock_get_time
clock_serv_t cclock;
mach_timespec_t mts;
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock);
time.tv_sec = mts.tv_sec;
time.tv_nsec = mts.tv_nsec;
# else
clock_gettime(CLOCK_REALTIME, &time);
# endif
return time;
}
timespec
timespec_from_ms (const uint32_t millis)
{
timespec time;
time.tv_sec = millis / 1e3;
time.tv_nsec = (millis - (time.tv_sec * 1e3)) * 1e6;
return time;
}
Serial::SerialImpl::SerialImpl (const string &port, unsigned long baudrate, Serial::SerialImpl::SerialImpl (const string &port, unsigned long baudrate,
bytesize_t bytesize, bytesize_t bytesize,
parity_t parity, stopbits_t stopbits, parity_t parity, stopbits_t stopbits,
@ -266,7 +316,7 @@ Serial::SerialImpl::reconfigurePort ()
} }
// set custom divisor // set custom divisor
ser.custom_divisor = ser.baud_base / (int) baudrate_; ser.custom_divisor = ser.baud_base / static_cast<int> (baudrate_);
// update flags // update flags
ser.flags &= ~ASYNC_SPD_MASK; ser.flags &= ~ASYNC_SPD_MASK;
ser.flags |= ASYNC_SPD_CUST; ser.flags |= ASYNC_SPD_CUST;
@ -404,35 +454,6 @@ Serial::SerialImpl::available ()
} }
} }
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;
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock);
time.tv_sec = mts.tv_sec;
time.tv_nsec = mts.tv_nsec;
# else
clock_gettime(CLOCK_REALTIME, &time);
# endif
}
inline void
diff_timespec (timespec &start, timespec &end, timespec &result) {
if (start.tv_sec > end.tv_sec) {
throw SerialException ("Timetravel, start time later than end time.");
}
result.tv_sec = end.tv_sec - start.tv_sec;
result.tv_nsec = end.tv_nsec - start.tv_nsec;
if (result.tv_nsec < 0) {
result.tv_nsec = 1e9 - result.tv_nsec;
result.tv_sec -= 1;
}
}
size_t size_t
Serial::SerialImpl::read (uint8_t *buf, size_t size) Serial::SerialImpl::read (uint8_t *buf, size_t size)
{ {
@ -442,58 +463,37 @@ Serial::SerialImpl::read (uint8_t *buf, size_t size)
} }
fd_set readfds; fd_set readfds;
size_t bytes_read = 0; size_t bytes_read = 0;
// 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) // Calculate total timeout in milliseconds t_c + (t_m * N)
long total_timeout_ms = timeout_.read_timeout_constant; long total_timeout_ms = timeout_.read_timeout_constant;
total_timeout_ms += timeout_.read_timeout_multiplier * static_cast<long> (size); total_timeout_ms += timeout_.read_timeout_multiplier * static_cast<long> (size);
total_timeout.tv_sec = total_timeout_ms / 1000; MillisecondTimer total_timeout(total_timeout_ms);
total_timeout.tv_usec = static_cast<int>(total_timeout_ms % 1000);
total_timeout.tv_usec *= 1000; // To convert to micro seconds // Pre-fill buffer with available bytes
// 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<int> (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 ssize_t bytes_read_now = ::read (fd_, buf, size);
timeout = total_timeout; if (bytes_read_now > 0) {
} else { bytes_read = bytes_read_now;
// Else set the select timeout to use the inter byte time
timeout = inter_byte_timeout;
} }
}
while (bytes_read < size) {
int64_t timeout_remaining_ms = total_timeout.remaining();
if (timeout_remaining_ms <= 0) {
// Timed out
break;
}
// 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<uint32_t> (timeout_remaining_ms),
timeout_.inter_byte_timeout)));
FD_ZERO (&readfds); FD_ZERO (&readfds);
FD_SET (fd_, &readfds); FD_SET (fd_, &readfds);
// Begin timing select
struct timespec start, end;
get_time_now (start);
// Call select to block for serial data or a timeout // Call select to block for serial data or a timeout
int r = select (fd_ + 1, &readfds, NULL, NULL, &timeout); int r = pselect (fd_ + 1, &readfds, NULL, NULL, &timeout, NULL);
// Calculate difference and update the structure
get_time_now (end);
// Calculate the time select took
struct timespec diff;
diff_timespec (start, end, diff);
// Update the timeout
if (total_timeout.tv_sec <= diff.tv_sec) {
total_timeout.tv_sec = 0;
} else {
total_timeout.tv_sec -= diff.tv_sec;
}
if (total_timeout.tv_usec <= (diff.tv_nsec / 1000)) {
total_timeout.tv_usec = 0;
} else {
total_timeout.tv_usec -= (diff.tv_nsec / 1000);
}
// Figure out what happened by looking at select's response 'r' // Figure out what happened by looking at select's response 'r'
/** Error **/ /** Error **/
@ -559,41 +559,25 @@ Serial::SerialImpl::write (const uint8_t *data, size_t length)
} }
fd_set writefds; fd_set writefds;
size_t bytes_written = 0; size_t bytes_written = 0;
struct timeval timeout;
timeout.tv_sec = timeout_.write_timeout_constant / 1000; // Calculate total timeout in milliseconds t_c + (t_m * N)
timeout.tv_usec = static_cast<int> (timeout_.write_timeout_multiplier % 1000); long total_timeout_ms = timeout_.write_timeout_constant;
timeout.tv_usec *= 1000; // To convert to micro seconds total_timeout_ms += timeout_.write_timeout_multiplier * static_cast<long> (length);
MillisecondTimer total_timeout(total_timeout_ms);
while (bytes_written < length) { while (bytes_written < length) {
int64_t timeout_remaining_ms = total_timeout.remaining();
if (timeout_remaining_ms <= 0) {
// Timed out
break;
}
timespec timeout(timespec_from_ms(timeout_remaining_ms));
FD_ZERO (&writefds); FD_ZERO (&writefds);
FD_SET (fd_, &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 // Do the select
int r = select (fd_ + 1, NULL, &writefds, NULL, &timeout); int r = pselect (fd_ + 1, NULL, &writefds, NULL, &timeout, NULL);
#if !defined(__linux__)
// Calculate difference and update the structure
get_time_now(end);
// Calculate the time select took
struct timespec diff;
diff_timespec(start, end, diff);
// 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_nsec / 1000)) {
timeout.tv_usec = 0;
} else {
timeout.tv_usec -= (diff.tv_nsec / 1000);
}
#endif
// Figure out what happened by looking at select's response 'r' // Figure out what happened by looking at select's response 'r'
/** Error **/ /** Error **/

10
tests/CMakeLists.txt Normal file
View File

@ -0,0 +1,10 @@
if(UNIX)
catkin_add_gtest(${PROJECT_NAME}-test unix_serial_tests.cc)
target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME} ${Boost_LIBRARIES})
if(NOT APPLE)
target_link_libraries(${PROJECT_NAME}-test util)
endif()
catkin_add_gtest(${PROJECT_NAME}-test-timer unit/unix_timer_tests.cc)
target_link_libraries(${PROJECT_NAME}-test-timer ${PROJECT_NAME})
endif()

View File

@ -0,0 +1,63 @@
#include "gtest/gtest.h"
#include "serial/impl/unix.h"
#include <unistd.h>
#include <stdlib.h>
using serial::MillisecondTimer;
namespace {
/**
* Do 100 trials of timing gaps between 0 and 19 milliseconds.
* Expect accuracy within one millisecond.
*/
TEST(timer_tests, short_intervals) {
for (int trial = 0; trial < 100; trial++)
{
uint32_t ms = rand() % 20;
MillisecondTimer mt(ms);
usleep(1000 * ms);
int32_t r = mt.remaining();
// 1ms slush, for the cost of calling usleep.
EXPECT_NEAR(r+1, 0, 1);
}
}
TEST(timer_tests, overlapping_long_intervals) {
MillisecondTimer* timers[10];
// Experimentally determined. Corresponds to the extra time taken by the loops,
// the big usleep, and the test infrastructure itself.
const int slush_factor = 14;
// Set up the timers to each time one second, 1ms apart.
for (int t = 0; t < 10; t++)
{
timers[t] = new MillisecondTimer(1000);
usleep(1000);
}
// Check in on them after 500ms.
usleep(500000);
for (int t = 0; t < 10; t++)
{
EXPECT_NEAR(timers[t]->remaining(), 500 - slush_factor + t, 5);
}
// Check in on them again after another 500ms and free them.
usleep(500000);
for (int t = 0; t < 10; t++)
{
EXPECT_NEAR(timers[t]->remaining(), -slush_factor + t, 5);
delete timers[t];
}
}
} // namespace
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}