mavtables  0.2.1
MAVLink router and firewall.
UnixSerialPort.cpp
Go to the documentation of this file.
1 // MAVLink router and firewall.
2 // Copyright (C) 2018 Michael R. Shannon <mrshannon.aerospace@gmail.com>
3 //
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 2 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 
17 
18 #include <algorithm>
19 #include <chrono>
20 #include <cstdint>
21 #include <iterator>
22 #include <memory>
23 #include <stdexcept>
24 #include <system_error>
25 #include <utility>
26 #include <vector>
27 
28 #include "PartialSendError.hpp"
29 #include "UnixSerialPort.hpp"
30 #include "UnixSyscalls.hpp"
31 
32 
33 /** Construct a serial port.
34  *
35  * \param device The string representing the serial port. For example
36  * "/dev/ttyUSB0".
37  * \param baud_rate The baud rate in bits per second, the default value is 9600
38  * bps.
39  * \param features A bitflag of the features to enable, default is to not
40  * enable any features. See \ref SerialPort::Feature for flags.
41  * \param syscalls The object to use for unix system calls. It is default
42  * constructed to the production implementation. This argument is only
43  * used for testing.
44  * \throws std::invalid_argument if the baud rate is not supported.
45  * \throws std::system_error if a system call produces an error.
46  */
48  std::string device,
49  unsigned long baud_rate,
50  SerialPort::Feature features,
51  std::unique_ptr<UnixSyscalls> syscalls)
52  : device_(std::move(device)), baud_rate_(baud_rate),
53  features_(features), syscalls_(std::move(syscalls)), port_(-1)
54 {
55  open_port_();
56 }
57 
58 
59 /** The serial port destructor.
60  *
61  * Closes the underlying file descriptor of the serial port device.
62  */
63 // LCOV_EXCL_START
65 {
66  syscalls_->close(port_);
67 }
68 // LCOV_EXCL_STOP
69 
70 
71 /** \copydoc SerialPort::read(const std::chrono::nanoseconds &)
72  *
73  * \note The timeout precision of this implementation is 1 millisecond.
74  *
75  * \throws std::system_error if a system call produces an error.
76  */
77 std::vector<uint8_t> UnixSerialPort::read(
78  const std::chrono::nanoseconds &timeout)
79 {
80  std::chrono::milliseconds timeout_ms =
81  std::chrono::duration_cast<std::chrono::milliseconds>(timeout);
82  struct pollfd fds = {port_, POLLIN, 0};
83  auto result = syscalls_->poll(
84  &fds, 1, static_cast<int>(timeout_ms.count()));
85 
86  // Poll error
87  if (result < 0)
88  {
89  throw std::system_error(std::error_code(errno, std::system_category()));
90  }
91  // Success
92  else if (result > 0)
93  {
94  // Port error
95  if (fds.revents & POLLERR)
96  {
97  syscalls_->close(port_);
98  open_port_();
99  return std::vector<uint8_t>();
100  }
101  // Data available for reading.
102  else if (fds.revents & POLLIN)
103  {
104  return read_();
105  }
106  }
107 
108  // Timed out
109  return std::vector<uint8_t>();
110 }
111 
112 
113 /** \copydoc SerialPort::write(const std::vector<uint8_t> &)
114  *
115  * \throws std::system_error if a system call produces an error.
116  * \throws PartialSendError if it fails to write all the data it is given.
117  */
118 void UnixSerialPort::write(const std::vector<uint8_t> &data)
119 {
120  // Write the data.
121  auto err = syscalls_->write(port_, data.data(), data.size());
122 
123  // Handle system call errors.
124  if (err < 0)
125  {
126  throw std::system_error(std::error_code(errno, std::system_category()));
127  }
128 
129  // Could not write all data.
130  if (static_cast<size_t>(err) < data.size())
131  {
132  throw PartialSendError(static_cast<unsigned long>(err), data.size());
133  }
134 }
135 
136 /** Configure serial port.
137  *
138  * \param baud_rate The bitrate to configure for the port.
139  * \param features A bitmask representing the features to use. See \ref
140  * SerialPort::Feature for documentation.
141  * \throws std::invalid_argument if the baud rate is not supported.
142  * \throws std::system_error if a system call produces an error.
143  */
144 void UnixSerialPort::configure_port_(
145  unsigned long baud_rate, SerialPort::Feature features)
146 {
147  // Get attribute structure.
148  struct termios tty;
149 
150  if (syscalls_->tcgetattr(port_, &tty) < 0)
151  {
152  throw std::system_error(std::error_code(errno, std::system_category()));
153  }
154 
155  // Set baud rate.
156  speed_t speed = speed_constant_(baud_rate);
157 
158  if (cfsetispeed(&tty, speed) < 0)
159  {
160  // This is unreachable assuming the speed_constant_ method is properly
161  // written.
162  // LCOV_EXCL_START
163  throw std::system_error(std::error_code(errno, std::system_category()));
164  // LCOV_EXCL_STOP
165  }
166 
167  if (cfsetospeed(&tty, speed) < 0)
168  {
169  // This is unreachable assuming the speed_constant_ method is properly
170  // written.
171  // LCOV_EXCL_START
172  throw std::system_error(std::error_code(errno, std::system_category()));
173  // LCOV_EXCL_STOP
174  }
175 
176  // Enable receiver and set local mode.
177  tty.c_cflag |= static_cast<tcflag_t>(CLOCAL | CREAD);
178  // Use 8N1 mode (8 data bits, no parity, 1 stop bit).
179  tty.c_cflag &= static_cast<tcflag_t>(~PARENB);
180  tty.c_cflag &= static_cast<tcflag_t>(~CSTOPB);
181  tty.c_cflag &= static_cast<tcflag_t>(~CSIZE);
182  tty.c_cflag |= static_cast<tcflag_t>(CS8);
183 
184  // Enable/disable hardware flow control.
185  if (features & SerialPort::FLOW_CONTROL)
186  {
187  tty.c_cflag |= static_cast<tcflag_t>(CRTSCTS);
188  }
189  else
190  {
191  tty.c_cflag &= static_cast<tcflag_t>(~CRTSCTS);
192  }
193 
194  // Use raw input.
195  tty.c_lflag &= static_cast<tcflag_t>(~(ICANON | ECHO | ECHOE | ISIG));
196  tty.c_iflag &= static_cast<tcflag_t>(~(IGNBRK | BRKINT | PARMRK | ISTRIP));
197  tty.c_iflag &= static_cast<tcflag_t>(~(INLCR | IGNCR | ICRNL));
198  // Disable software flow control.
199  tty.c_iflag &= static_cast<tcflag_t>(~(IXON | IXOFF | IXANY));
200  // Use raw output.
201  tty.c_oflag &= static_cast<tcflag_t>(~OPOST);
202  // Non blocking mode, using poll.
203  // See: http://unixwiz.net/techtips/termios-vmin-vtime.html
204  tty.c_cc[VMIN] = 0;
205  tty.c_cc[VTIME] = 0;
206 
207  // Apply settings to serial port.
208  if (syscalls_->tcsetattr(port_, TCSANOW, &tty) < 0)
209  {
210  throw std::system_error(std::error_code(errno, std::system_category()));
211  }
212 }
213 
214 
215 /** Open the serial port.
216  *
217  * \throws std::invalid_argument if the baud rate is not supported.
218  * \throws std::system_error if a system call produces an error.
219  */
220 void UnixSerialPort::open_port_()
221 {
222  port_ = -1;
223  // Open serial port.
224  port_ = syscalls_->open(device_.c_str(), O_RDWR | O_NOCTTY | O_SYNC);
225 
226  if (port_ < 0)
227  {
228  throw std::system_error(
229  std::error_code(errno, std::system_category()),
230  "Failed to open \"" + device_ + "\".");
231  }
232 
233  // Configure serial port.
234  try
235  {
236  configure_port_(baud_rate_, features_);
237  }
238  catch (...)
239  {
240  syscalls_->close(port_);
241  throw;
242  }
243 }
244 
245 
246 /** Read data from the serial port.
247  *
248  * \warning There must be data to read, otherwise calling this method is
249  * undefined.
250  *
251  * \returns The data read from the port, up to 1024 bytes at a time.
252  * \throws std::system_error if a system call produces an error.
253  */
254 std::vector<uint8_t> UnixSerialPort::read_()
255 {
256  std::vector<uint8_t> buffer;
257  buffer.resize(1024);
258  auto size = syscalls_->read(port_, buffer.data(), buffer.size());
259 
260  if (size < 0)
261  {
262  throw std::system_error(std::error_code(errno, std::system_category()));
263  }
264 
265  buffer.resize(static_cast<size_t>(size));
266  return buffer;
267 }
268 
269 
270 /** Convert integer to termios baud rate constant.
271  *
272  * See \ref UnixSerialPort::UnixSerialPort for valid baud rates.
273  *
274  * \param baud_rate The baud rate to convert.
275  * \returns The termios baud rate constant.
276  * \throws std::invalid_argument if the given baud rate is not supported.
277  */
278 speed_t UnixSerialPort::speed_constant_(unsigned long baud_rate)
279 {
280  switch (baud_rate)
281  {
282  case 0:
283  return B0;
284 
285  case 50:
286  return B50;
287 
288  case 75:
289  return B75;
290 
291  case 110:
292  return B110;
293 
294  case 134: // actually 134.5
295  case 135: // actually 134.5
296  return B134;
297 
298  case 150:
299  return B150;
300 
301  case 200:
302  return B200;
303 
304  case 300:
305  return B300;
306 
307  case 600:
308  return B600;
309 
310  case 1200:
311  return B1200;
312 
313  case 1800:
314  return B1800;
315 
316  case 2400:
317  return B2400;
318 
319  case 4800:
320  return B4800;
321 
322  case 9600:
323  return B9600;
324 
325  case 19200:
326  return B19200;
327 
328  case 38400:
329  return B38400;
330 
331  case 57600:
332  return B57600;
333 
334  case 115200:
335  return B115200;
336 
337  case 230400:
338  return B230400;
339 
340  default:
341  throw std::invalid_argument(
342  std::to_string(baud_rate) + " bps is not a valid baud rate.");
343  }
344 }
345 
346 
347 /** \copydoc SerialPort::print_(std::ostream &os)const
348  *
349  * Example:
350  * ```
351  * serial {
352  * device /dev/ttyUSB0;
353  * baudrate 115200;
354  * flow_control yes;
355  * }
356  * ```
357  *
358  * \param os The output stream to print to.
359  */
360 std::ostream &UnixSerialPort::print_(std::ostream &os) const
361 {
362  os << "serial {" << std::endl;
363  os << " device " << device_ << ";" << std::endl;
364  os << " baudrate " << std::to_string(baud_rate_) << ";" << std::endl;
365 
366  if ((features_ & SerialPort::FLOW_CONTROL) != 0)
367  {
368  os << " flow_control yes;" << std::endl;
369  }
370  else
371  {
372  os << " flow_control no;" << std::endl;
373  }
374 
375  os << "}";
376  return os;
377 }
STL namespace.
virtual ~UnixSerialPort()
Enable flow control.
Definition: SerialPort.hpp:62
virtual void write(const std::vector< uint8_t > &data) final
virtual std::vector< uint8_t > read(const std::chrono::nanoseconds &timeout=std::chrono::nanoseconds::zero()) final
std::ostream & print_(std::ostream &os) const final
UnixSerialPort(std::string device, unsigned long baud_rate=9600, SerialPort::Feature features=SerialPort::DEFAULT, std::unique_ptr< UnixSyscalls > syscalls=std::make_unique< UnixSyscalls >())