1085 lines
26 KiB
C++
1085 lines
26 KiB
C++
/* Copyright 2012 William Woodall and John Harrison
|
|
*
|
|
* Additional Contributors: Christopher Baker @bakercp
|
|
*/
|
|
|
|
#if !defined(_WIN32)
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sstream>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/signal.h>
|
|
#include <errno.h>
|
|
#include <paths.h>
|
|
#include <sysexits.h>
|
|
#include <termios.h>
|
|
#include <sys/param.h>
|
|
#include <pthread.h>
|
|
|
|
#if defined(__linux__)
|
|
# include <linux/serial.h>
|
|
#endif
|
|
|
|
#include <sys/select.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#ifdef __MACH__
|
|
#include <AvailabilityMacros.h>
|
|
#include <mach/clock.h>
|
|
#include <mach/mach.h>
|
|
#endif
|
|
|
|
#include "serial/impl/unix.h"
|
|
|
|
#ifndef TIOCINQ
|
|
#ifdef FIONREAD
|
|
#define TIOCINQ FIONREAD
|
|
#else
|
|
#define TIOCINQ 0x541B
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(MAC_OS_X_VERSION_10_3) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_3)
|
|
#include <IOKit/serial/ioss.h>
|
|
#endif
|
|
|
|
using std::string;
|
|
using std::stringstream;
|
|
using std::invalid_argument;
|
|
using serial::MillisecondTimer;
|
|
using serial::Serial;
|
|
using serial::SerialException;
|
|
using serial::PortNotOpenedException;
|
|
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);
|
|
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(), SYSTEM_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_MONOTONIC, &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,
|
|
bytesize_t bytesize,
|
|
parity_t parity, stopbits_t stopbits,
|
|
flowcontrol_t flowcontrol)
|
|
: port_ (port), fd_ (-1), is_open_ (false), xonxoff_ (false), 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);
|
|
if (port_.empty () == false)
|
|
open ();
|
|
}
|
|
|
|
Serial::SerialImpl::~SerialImpl ()
|
|
{
|
|
close();
|
|
pthread_mutex_destroy(&this->read_mutex);
|
|
pthread_mutex_destroy(&this->write_mutex);
|
|
}
|
|
|
|
void
|
|
Serial::SerialImpl::open ()
|
|
{
|
|
if (port_.empty ()) {
|
|
throw invalid_argument ("Empty port is invalid.");
|
|
}
|
|
if (is_open_ == true) {
|
|
throw SerialException ("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);
|
|
}
|
|
}
|
|
|
|
reconfigurePort();
|
|
is_open_ = true;
|
|
}
|
|
|
|
void
|
|
Serial::SerialImpl::reconfigurePort ()
|
|
{
|
|
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) {
|
|
THROW (IOException, "::tcgetattr");
|
|
}
|
|
|
|
// set up raw mode / no echo / binary
|
|
options.c_cflag |= (tcflag_t) (CLOCAL | CREAD);
|
|
options.c_lflag &= (tcflag_t) ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL |
|
|
ISIG | IEXTEN); //|ECHOPRT
|
|
|
|
options.c_oflag &= (tcflag_t) ~(OPOST);
|
|
options.c_iflag &= (tcflag_t) ~(INLCR | IGNCR | ICRNL | IGNBRK);
|
|
#ifdef IUCLC
|
|
options.c_iflag &= (tcflag_t) ~IUCLC;
|
|
#endif
|
|
#ifdef PARMRK
|
|
options.c_iflag &= (tcflag_t) ~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 B500000
|
|
case 500000: baud = B500000; break;
|
|
#endif
|
|
#ifdef B576000
|
|
case 576000: baud = B576000; break;
|
|
#endif
|
|
#ifdef B921600
|
|
case 921600: baud = B921600; break;
|
|
#endif
|
|
#ifdef B1000000
|
|
case 1000000: baud = B1000000; break;
|
|
#endif
|
|
#ifdef B1152000
|
|
case 1152000: baud = B1152000; break;
|
|
#endif
|
|
#ifdef B1500000
|
|
case 1500000: baud = B1500000; break;
|
|
#endif
|
|
#ifdef B2000000
|
|
case 2000000: baud = B2000000; break;
|
|
#endif
|
|
#ifdef B2500000
|
|
case 2500000: baud = B2500000; break;
|
|
#endif
|
|
#ifdef B3000000
|
|
case 3000000: baud = B3000000; break;
|
|
#endif
|
|
#ifdef B3500000
|
|
case 3500000: baud = B3500000; break;
|
|
#endif
|
|
#ifdef B4000000
|
|
case 4000000: baud = B4000000; break;
|
|
#endif
|
|
default:
|
|
custom_baud = true;
|
|
}
|
|
if (custom_baud == false) {
|
|
#ifdef _BSD_SOURCE
|
|
::cfsetspeed(&options, baud);
|
|
#else
|
|
::cfsetispeed(&options, baud);
|
|
::cfsetospeed(&options, baud);
|
|
#endif
|
|
}
|
|
|
|
// setup char len
|
|
options.c_cflag &= (tcflag_t) ~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 &= (tcflag_t) ~(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);
|
|
else if (stopbits_ == stopbits_two)
|
|
options.c_cflag |= (CSTOPB);
|
|
else
|
|
throw invalid_argument ("invalid stop bit");
|
|
// setup parity
|
|
options.c_iflag &= (tcflag_t) ~(INPCK | ISTRIP);
|
|
if (parity_ == parity_none) {
|
|
options.c_cflag &= (tcflag_t) ~(PARENB | PARODD);
|
|
} else if (parity_ == parity_even) {
|
|
options.c_cflag &= (tcflag_t) ~(PARODD);
|
|
options.c_cflag |= (PARENB);
|
|
} else if (parity_ == parity_odd) {
|
|
options.c_cflag |= (PARENB | PARODD);
|
|
}
|
|
#ifdef CMSPAR
|
|
else if (parity_ == parity_mark) {
|
|
options.c_cflag |= (PARENB | CMSPAR | PARODD);
|
|
}
|
|
else if (parity_ == parity_space) {
|
|
options.c_cflag |= (PARENB | CMSPAR);
|
|
options.c_cflag &= (tcflag_t) ~(PARODD);
|
|
}
|
|
#else
|
|
// CMSPAR is not defined on OSX. So do not support mark or space parity.
|
|
else if (parity_ == parity_mark || parity_ == parity_space) {
|
|
throw invalid_argument ("OS does not support mark or space parity");
|
|
}
|
|
#endif // ifdef CMSPAR
|
|
else {
|
|
throw invalid_argument ("invalid parity");
|
|
}
|
|
// setup flow control
|
|
if (flowcontrol_ == flowcontrol_none) {
|
|
xonxoff_ = false;
|
|
rtscts_ = false;
|
|
}
|
|
if (flowcontrol_ == flowcontrol_software) {
|
|
xonxoff_ = true;
|
|
rtscts_ = false;
|
|
}
|
|
if (flowcontrol_ == flowcontrol_hardware) {
|
|
xonxoff_ = false;
|
|
rtscts_ = true;
|
|
}
|
|
// xonxoff
|
|
#ifdef IXANY
|
|
if (xonxoff_)
|
|
options.c_iflag |= (IXON | IXOFF); //|IXANY)
|
|
else
|
|
options.c_iflag &= (tcflag_t) ~(IXON | IXOFF | IXANY);
|
|
#else
|
|
if (xonxoff_)
|
|
options.c_iflag |= (IXON | IXOFF);
|
|
else
|
|
options.c_iflag &= (tcflag_t) ~(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
|
|
|
|
// 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
|
|
// to read before each call, so we should never needlessly poll
|
|
options.c_cc[VMIN] = 0;
|
|
options.c_cc[VTIME] = 0;
|
|
|
|
// activate settings
|
|
::tcsetattr (fd_, TCSANOW, &options);
|
|
|
|
// apply custom baud rate, if any
|
|
if (custom_baud == true) {
|
|
// OS X support
|
|
#if defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4)
|
|
// Starting with Tiger, the IOSSIOSPEED ioctl can be used to set arbitrary baud rates
|
|
// other than those specified by POSIX. The driver for the underlying serial hardware
|
|
// ultimately determines which baud rates can be used. This ioctl sets both the input
|
|
// and output speed.
|
|
speed_t new_baud = static_cast<speed_t> (baudrate_);
|
|
// PySerial uses IOSSIOSPEED=0x80045402
|
|
if (-1 == ioctl (fd_, IOSSIOSPEED, &new_baud, 1)) {
|
|
THROW (IOException, errno);
|
|
}
|
|
// Linux Support
|
|
#elif defined(__linux__) && defined (TIOCSSERIAL)
|
|
struct serial_struct ser;
|
|
|
|
if (-1 == ioctl (fd_, TIOCGSERIAL, &ser)) {
|
|
THROW (IOException, errno);
|
|
}
|
|
|
|
// set custom divisor
|
|
ser.custom_divisor = ser.baud_base / static_cast<int> (baudrate_);
|
|
// update flags
|
|
ser.flags &= ~ASYNC_SPD_MASK;
|
|
ser.flags |= ASYNC_SPD_CUST;
|
|
|
|
if (-1 == ioctl (fd_, TIOCSSERIAL, &ser)) {
|
|
THROW (IOException, errno);
|
|
}
|
|
#else
|
|
throw invalid_argument ("OS does not currently support custom bauds");
|
|
#endif
|
|
}
|
|
|
|
// 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
|
|
Serial::SerialImpl::close ()
|
|
{
|
|
if (is_open_ == true) {
|
|
if (fd_ != -1) {
|
|
int ret;
|
|
ret = ::close (fd_);
|
|
if (ret == 0) {
|
|
fd_ = -1;
|
|
} else {
|
|
THROW (IOException, errno);
|
|
}
|
|
}
|
|
is_open_ = false;
|
|
}
|
|
}
|
|
|
|
bool
|
|
Serial::SerialImpl::isOpen () const
|
|
{
|
|
return is_open_;
|
|
}
|
|
|
|
size_t
|
|
Serial::SerialImpl::available ()
|
|
{
|
|
if (!is_open_) {
|
|
return 0;
|
|
}
|
|
int count = 0;
|
|
if (-1 == ioctl (fd_, TIOCINQ, &count)) {
|
|
THROW (IOException, errno);
|
|
} else {
|
|
return static_cast<size_t> (count);
|
|
}
|
|
}
|
|
|
|
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);
|
|
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)
|
|
{
|
|
timespec wait_time = { 0, static_cast<long>(byte_time_ns_ * count)};
|
|
pselect (0, NULL, NULL, NULL, &wait_time, NULL);
|
|
}
|
|
|
|
size_t
|
|
Serial::SerialImpl::read (uint8_t *buf, size_t size)
|
|
{
|
|
// If the port is not open, throw
|
|
if (!is_open_) {
|
|
throw PortNotOpenedException ("Serial::read");
|
|
}
|
|
size_t bytes_read = 0;
|
|
|
|
// 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<long> (size);
|
|
MillisecondTimer total_timeout(total_timeout_ms);
|
|
|
|
// Pre-fill buffer with available bytes
|
|
{
|
|
ssize_t bytes_read_now = ::read (fd_, buf, size);
|
|
if (bytes_read_now > 0) {
|
|
bytes_read = bytes_read_now;
|
|
}
|
|
}
|
|
|
|
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.
|
|
uint32_t timeout = std::min(static_cast<uint32_t> (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 =
|
|
::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<size_t> (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!");
|
|
}
|
|
}
|
|
}
|
|
return bytes_read;
|
|
}
|
|
|
|
size_t
|
|
Serial::SerialImpl::write (const uint8_t *data, size_t length)
|
|
{
|
|
if (is_open_ == false) {
|
|
throw PortNotOpenedException ("Serial::write");
|
|
}
|
|
fd_set writefds;
|
|
size_t bytes_written = 0;
|
|
|
|
// Calculate total timeout in milliseconds t_c + (t_m * N)
|
|
long total_timeout_ms = timeout_.write_timeout_constant;
|
|
total_timeout_ms += timeout_.write_timeout_multiplier * static_cast<long> (length);
|
|
MillisecondTimer total_timeout(total_timeout_ms);
|
|
|
|
bool first_iteration = true;
|
|
while (bytes_written < length) {
|
|
int64_t timeout_remaining_ms = total_timeout.remaining();
|
|
// Only consider the timeout if it's not the first iteration of the loop
|
|
// otherwise a timeout of 0 won't be allowed through
|
|
if (!first_iteration && (timeout_remaining_ms <= 0)) {
|
|
// Timed out
|
|
break;
|
|
}
|
|
first_iteration = false;
|
|
|
|
timespec timeout(timespec_from_ms(timeout_remaining_ms));
|
|
|
|
FD_ZERO (&writefds);
|
|
FD_SET (fd_, &writefds);
|
|
|
|
// Do the select
|
|
int r = pselect (fd_ + 1, NULL, &writefds, 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) {
|
|
continue;
|
|
}
|
|
// Otherwise there was some error
|
|
THROW (IOException, errno);
|
|
}
|
|
/** Timeout **/
|
|
if (r == 0) {
|
|
break;
|
|
}
|
|
/** Port ready to write **/
|
|
if (r > 0) {
|
|
// Make sure our file descriptor is in the ready to write list
|
|
if (FD_ISSET (fd_, &writefds)) {
|
|
// This will write some
|
|
ssize_t bytes_written_now =
|
|
::write (fd_, data + bytes_written, length - bytes_written);
|
|
|
|
// even though pselect returned readiness the call might still be
|
|
// interrupted. In that case simply retry.
|
|
if (bytes_written_now == -1 && errno == EINTR) {
|
|
continue;
|
|
}
|
|
|
|
// write should always return some data as select reported it was
|
|
// ready to write 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 write immediately
|
|
// but writing returns nothing.
|
|
std::stringstream strs;
|
|
strs << "device reports readiness to write but "
|
|
"returned no data (device disconnected?)";
|
|
strs << " errno=" << errno;
|
|
strs << " bytes_written_now= " << bytes_written_now;
|
|
strs << " bytes_written=" << bytes_written;
|
|
strs << " length=" << length;
|
|
throw SerialException(strs.str().c_str());
|
|
}
|
|
// Update bytes_written
|
|
bytes_written += static_cast<size_t> (bytes_written_now);
|
|
// If bytes_written == size then we have written everything we need to
|
|
if (bytes_written == length) {
|
|
break;
|
|
}
|
|
// If bytes_written < size then we have more to write
|
|
if (bytes_written < length) {
|
|
continue;
|
|
}
|
|
// If bytes_written > size then we have over written, which shouldn't happen
|
|
if (bytes_written > length) {
|
|
throw SerialException ("write over wrote, too many bytes where "
|
|
"written, 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 write, but our fd isn't"
|
|
" in the list, this shouldn't happen!");
|
|
}
|
|
}
|
|
return 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;
|
|
}
|
|
|
|
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");
|
|
}
|
|
tcdrain (fd_);
|
|
}
|
|
|
|
void
|
|
Serial::SerialImpl::flushInput ()
|
|
{
|
|
if (is_open_ == false) {
|
|
throw PortNotOpenedException ("Serial::flushInput");
|
|
}
|
|
tcflush (fd_, TCIFLUSH);
|
|
}
|
|
|
|
void
|
|
Serial::SerialImpl::flushOutput ()
|
|
{
|
|
if (is_open_ == false) {
|
|
throw PortNotOpenedException ("Serial::flushOutput");
|
|
}
|
|
tcflush (fd_, TCOFLUSH);
|
|
}
|
|
|
|
void
|
|
Serial::SerialImpl::sendBreak (int duration)
|
|
{
|
|
if (is_open_ == false) {
|
|
throw PortNotOpenedException ("Serial::sendBreak");
|
|
}
|
|
tcsendbreak (fd_, static_cast<int> (duration / 4));
|
|
}
|
|
|
|
void
|
|
Serial::SerialImpl::setBreak (bool level)
|
|
{
|
|
if (is_open_ == false) {
|
|
throw PortNotOpenedException ("Serial::setBreak");
|
|
}
|
|
|
|
if (level) {
|
|
if (-1 == ioctl (fd_, TIOCSBRK))
|
|
{
|
|
stringstream ss;
|
|
ss << "setBreak failed on a call to ioctl(TIOCSBRK): " << errno << " " << strerror(errno);
|
|
throw(SerialException(ss.str().c_str()));
|
|
}
|
|
} else {
|
|
if (-1 == ioctl (fd_, TIOCCBRK))
|
|
{
|
|
stringstream ss;
|
|
ss << "setBreak failed on a call to ioctl(TIOCCBRK): " << errno << " " << strerror(errno);
|
|
throw(SerialException(ss.str().c_str()));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Serial::SerialImpl::setRTS (bool level)
|
|
{
|
|
if (is_open_ == false) {
|
|
throw PortNotOpenedException ("Serial::setRTS");
|
|
}
|
|
|
|
int command = TIOCM_RTS;
|
|
|
|
if (level) {
|
|
if (-1 == ioctl (fd_, TIOCMBIS, &command))
|
|
{
|
|
stringstream ss;
|
|
ss << "setRTS failed on a call to ioctl(TIOCMBIS): " << errno << " " << strerror(errno);
|
|
throw(SerialException(ss.str().c_str()));
|
|
}
|
|
} else {
|
|
if (-1 == ioctl (fd_, TIOCMBIC, &command))
|
|
{
|
|
stringstream ss;
|
|
ss << "setRTS failed on a call to ioctl(TIOCMBIC): " << errno << " " << strerror(errno);
|
|
throw(SerialException(ss.str().c_str()));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Serial::SerialImpl::setDTR (bool level)
|
|
{
|
|
if (is_open_ == false) {
|
|
throw PortNotOpenedException ("Serial::setDTR");
|
|
}
|
|
|
|
int command = TIOCM_DTR;
|
|
|
|
if (level) {
|
|
if (-1 == ioctl (fd_, TIOCMBIS, &command))
|
|
{
|
|
stringstream ss;
|
|
ss << "setDTR failed on a call to ioctl(TIOCMBIS): " << errno << " " << strerror(errno);
|
|
throw(SerialException(ss.str().c_str()));
|
|
}
|
|
} else {
|
|
if (-1 == ioctl (fd_, TIOCMBIC, &command))
|
|
{
|
|
stringstream ss;
|
|
ss << "setDTR failed on a call to ioctl(TIOCMBIC): " << errno << " " << strerror(errno);
|
|
throw(SerialException(ss.str().c_str()));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
Serial::SerialImpl::waitForChange ()
|
|
{
|
|
#ifndef TIOCMIWAIT
|
|
|
|
while (is_open_ == true) {
|
|
|
|
int status;
|
|
|
|
if (-1 == ioctl (fd_, TIOCMGET, &status))
|
|
{
|
|
stringstream ss;
|
|
ss << "waitForChange failed on a call to ioctl(TIOCMGET): " << errno << " " << strerror(errno);
|
|
throw(SerialException(ss.str().c_str()));
|
|
}
|
|
else
|
|
{
|
|
if (0 != (status & TIOCM_CTS)
|
|
|| 0 != (status & TIOCM_DSR)
|
|
|| 0 != (status & TIOCM_RI)
|
|
|| 0 != (status & TIOCM_CD))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
usleep(1000);
|
|
}
|
|
|
|
return false;
|
|
#else
|
|
int command = (TIOCM_CD|TIOCM_DSR|TIOCM_RI|TIOCM_CTS);
|
|
|
|
if (-1 == ioctl (fd_, TIOCMIWAIT, &command)) {
|
|
stringstream ss;
|
|
ss << "waitForDSR failed on a call to ioctl(TIOCMIWAIT): "
|
|
<< errno << " " << strerror(errno);
|
|
throw(SerialException(ss.str().c_str()));
|
|
}
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
Serial::SerialImpl::getCTS ()
|
|
{
|
|
if (is_open_ == false) {
|
|
throw PortNotOpenedException ("Serial::getCTS");
|
|
}
|
|
|
|
int status;
|
|
|
|
if (-1 == ioctl (fd_, TIOCMGET, &status))
|
|
{
|
|
stringstream ss;
|
|
ss << "getCTS failed on a call to ioctl(TIOCMGET): " << errno << " " << strerror(errno);
|
|
throw(SerialException(ss.str().c_str()));
|
|
}
|
|
else
|
|
{
|
|
return 0 != (status & TIOCM_CTS);
|
|
}
|
|
}
|
|
|
|
bool
|
|
Serial::SerialImpl::getDSR ()
|
|
{
|
|
if (is_open_ == false) {
|
|
throw PortNotOpenedException ("Serial::getDSR");
|
|
}
|
|
|
|
int status;
|
|
|
|
if (-1 == ioctl (fd_, TIOCMGET, &status))
|
|
{
|
|
stringstream ss;
|
|
ss << "getDSR failed on a call to ioctl(TIOCMGET): " << errno << " " << strerror(errno);
|
|
throw(SerialException(ss.str().c_str()));
|
|
}
|
|
else
|
|
{
|
|
return 0 != (status & TIOCM_DSR);
|
|
}
|
|
}
|
|
|
|
bool
|
|
Serial::SerialImpl::getRI ()
|
|
{
|
|
if (is_open_ == false) {
|
|
throw PortNotOpenedException ("Serial::getRI");
|
|
}
|
|
|
|
int status;
|
|
|
|
if (-1 == ioctl (fd_, TIOCMGET, &status))
|
|
{
|
|
stringstream ss;
|
|
ss << "getRI failed on a call to ioctl(TIOCMGET): " << errno << " " << strerror(errno);
|
|
throw(SerialException(ss.str().c_str()));
|
|
}
|
|
else
|
|
{
|
|
return 0 != (status & TIOCM_RI);
|
|
}
|
|
}
|
|
|
|
bool
|
|
Serial::SerialImpl::getCD ()
|
|
{
|
|
if (is_open_ == false) {
|
|
throw PortNotOpenedException ("Serial::getCD");
|
|
}
|
|
|
|
int status;
|
|
|
|
if (-1 == ioctl (fd_, TIOCMGET, &status))
|
|
{
|
|
stringstream ss;
|
|
ss << "getCD failed on a call to ioctl(TIOCMGET): " << errno << " " << strerror(errno);
|
|
throw(SerialException(ss.str().c_str()));
|
|
}
|
|
else
|
|
{
|
|
return 0 != (status & TIOCM_CD);
|
|
}
|
|
}
|
|
|
|
void
|
|
Serial::SerialImpl::readLock ()
|
|
{
|
|
int result = pthread_mutex_lock(&this->read_mutex);
|
|
if (result) {
|
|
THROW (IOException, result);
|
|
}
|
|
}
|
|
|
|
void
|
|
Serial::SerialImpl::readUnlock ()
|
|
{
|
|
int result = pthread_mutex_unlock(&this->read_mutex);
|
|
if (result) {
|
|
THROW (IOException, result);
|
|
}
|
|
}
|
|
|
|
void
|
|
Serial::SerialImpl::writeLock ()
|
|
{
|
|
int result = pthread_mutex_lock(&this->write_mutex);
|
|
if (result) {
|
|
THROW (IOException, result);
|
|
}
|
|
}
|
|
|
|
void
|
|
Serial::SerialImpl::writeUnlock ()
|
|
{
|
|
int result = pthread_mutex_unlock(&this->write_mutex);
|
|
if (result) {
|
|
THROW (IOException, result);
|
|
}
|
|
}
|
|
|
|
#endif // !defined(_WIN32)
|