JeVois  1.20
JeVois Smart Embedded Machine Vision Toolkit
Share this page:
Serial.C
Go to the documentation of this file.
1 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2 //
3 // JeVois Smart Embedded Machine Vision Toolkit - Copyright (C) 2016 by Laurent Itti, the University of Southern
4 // California (USC), and iLab at USC. See http://iLab.usc.edu and http://jevois.org for information about this project.
5 //
6 // This file is part of the JeVois Smart Embedded Machine Vision Toolkit. This program is free software; you can
7 // redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software
8 // Foundation, version 2. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9 // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
10 // License for more details. You should have received a copy of the GNU General Public License along with this program;
11 // if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
12 //
13 // Contact information: Laurent Itti - 3641 Watt Way, HNB-07A - Los Angeles, CA 90089-2520 - USA.
14 // Tel: +1 213 740 3527 - itti@pollux.usc.edu - http://iLab.usc.edu - http://jevois.org
15 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
16 /*! \file */
17 
18 #include <jevois/Core/Serial.H>
19 #include <jevois/Core/Engine.H>
20 
21 #include <fstream>
22 
23 #include <fcntl.h>
24 #include <stdio.h>
25 #include <sys/stat.h>
26 #include <sys/types.h>
27 
28 // On first error, store the errno so we remember that we are in error:
29 #define SERFATAL(msg) do { \
30  if (itsErrno.load() == 0) itsErrno = errno; \
31  LFATAL('[' << instanceName() << "] " << msg << " (" << strerror(errno) << ')'); \
32  } while (0)
33 
34 #define SERTHROW(msg) do { \
35  if (itsErrno.load() == 0) itsErrno = errno; \
36  std::ostringstream ostr; \
37  ostr << '[' << instanceName() << "] " << msg << " (" << strerror(errno) << ')'; \
38  throw std::runtime_error(ostr.str()); \
39  } while (0)
40 
41 // ######################################################################
42 void jevois::Serial::tryReconnect()
43 {
44  std::lock_guard<std::mutex> _(itsMtx);
45 
46  if (itsOpenFut.valid() == false)
47  {
48  engine()->reportError('[' + instanceName() + "] connection lost -- Waiting for host to re-connect");
49  LINFO('[' << instanceName() << "] Waiting to reconnect to [" << jevois::serial::devname::get() << "] ...");
50  itsOpenFut = jevois::async_little([this]() { openPort(); });
51  }
52  else if (itsOpenFut.wait_for(std::chrono::milliseconds(5)) == std::future_status::ready)
53  try
54  {
55  itsOpenFut.get();
56  LINFO('[' << instanceName() << "] re-connected.");
57  } catch (...) { }
58 }
59 
60 // ######################################################################
61 jevois::Serial::Serial(std::string const & instance, jevois::UserInterface::Type type) :
62  jevois::UserInterface(instance), itsDev(-1), itsWriteOverflowCounter(0), itsType(type), itsErrno(0)
63 { }
64 
65 // ######################################################################
67 {
68  std::lock_guard<std::mutex> _(itsMtx);
69  openPort();
70 }
71 
72 // ######################################################################
73 void jevois::Serial::openPort()
74 {
75  // Open the port, non-blocking mode by default:
76  if (itsDev != -1) ::close(itsDev);
77  itsDev = ::open(jevois::serial::devname::get().c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
78  if (itsDev == -1) SERTHROW("Could not open serial port [" << jevois::serial::devname::get() << ']');
79 
80  // Save current state
81  if (tcgetattr(itsDev, &itsSavedState) == -1) SERTHROW("Failed to save current state");
82 
83  // reset all the flags
84  ////if (fcntl(itsDev, F_SETFL, 0) == -1) SERTHROW("Failed to reset flags");
85 
86  // Get the current option set:
87  termios options = { };
88  if (tcgetattr(itsDev, &options) == -1) SERTHROW("Failed to get options");
89 
90  // get raw input from the port
91  options.c_cflag |= ( CLOCAL // ignore modem control lines
92  | CREAD ); // enable the receiver
93 
94  options.c_iflag &= ~( IGNBRK // ignore BREAK condition on input
95  | BRKINT // If IGNBRK is not set, generate SIGINT on BREAK condition, else read BREAK as \0
96  | PARMRK
97  | ISTRIP // strip off eighth bit
98  | INLCR // donot translate NL to CR on input
99  | IGNCR // ignore CR
100  | ICRNL // translate CR to newline on input
101  | IXON // disable XON/XOFF flow control on output
102  );
103 
104  // disable implementation-defined output processing
105  options.c_oflag &= ~OPOST;
106  options.c_lflag &= ~(ECHO // dont echo i/p chars
107  | ECHONL // do not echo NL under any circumstance
108  | ICANON // disable canonical mode
109  | ISIG // do not signal for INTR, QUIT, SUSP etc
110  | IEXTEN // disable platform dependent i/p processing
111  );
112 
113  // Set the baudrate:
114  unsigned int rate;
116  {
117  case 4000000: rate = B4000000; break;
118  case 3500000: rate = B3500000; break;
119  case 3000000: rate = B3000000; break;
120  case 2500000: rate = B2500000; break;
121  case 2000000: rate = B2000000; break;
122  case 1500000: rate = B1500000; break;
123  case 1152000: rate = B1152000; break;
124  case 1000000: rate = B1000000; break;
125  case 921600: rate = B921600; break;
126  case 576000: rate = B576000; break;
127  case 500000: rate = B500000; break;
128  case 460800: rate = B460800; break;
129  case 230400: rate = B230400; break;
130  case 115200: rate = B115200; break;
131  case 57600: rate = B57600; break;
132  case 38400: rate = B38400; break;
133  case 19200: rate = B19200; break;
134  case 9600: rate = B9600; break;
135  case 4800: rate = B4800; break;
136  case 2400: rate = B2400; break;
137  case 1200: rate = B1200; break;
138  case 600: rate = B600; break;
139  case 300: rate = B300; break;
140  case 110: rate = B110; break;
141  case 0: rate = B0; break;
142  default: SERTHROW("Invalid baud rate " <<jevois::serial::baudrate::get());
143  }
144 
145  cfsetispeed(&options, rate);
146  cfsetospeed(&options, rate);
147 
148  // Parse the serial format string:
149  std::string const format = jevois::serial::format::get();
150  if (format.length() != 3) SERTHROW("Incorrect format string: " << format);
151 
152  // Set the number of bits:
153  options.c_cflag &= ~CSIZE; // mask off the 'size' bits
154 
155  switch (format[0])
156  {
157  case '5': options.c_cflag |= CS5; break;
158  case '6': options.c_cflag |= CS6; break;
159  case '7': options.c_cflag |= CS7; break;
160  case '8': options.c_cflag |= CS8; break;
161  default: SERTHROW("Invalid charbits: " << format[0] << " (should be 5..8)");
162  }
163 
164  // Set parity option:
165  options.c_cflag &= ~(PARENB | PARODD);
166 
167  switch(format[1])
168  {
169  case 'N': break;
170  case 'E': options.c_cflag |= PARENB; break;
171  case 'O': options.c_cflag |= (PARENB | PARODD); break;
172  default: SERTHROW("Invalid parity: " << format[1] << " (should be N,E,O)");
173  }
174 
175  // Set the stop bits option:
176  options.c_cflag &= ~CSTOPB;
177  switch(format[2])
178  {
179  case '1': break;
180  case '2': options.c_cflag |= CSTOPB; break;
181  default: SERTHROW("Invalid stopbits: " << format[2] << " (should be 1..2)");
182  }
183 
184  // Set the flow control:
185  options.c_cflag &= ~CRTSCTS;
186  options.c_iflag &= ~(IXON | IXANY | IXOFF);
187 
188  if (jevois::serial::flowsoft::get()) options.c_iflag |= (IXON | IXANY | IXOFF);
189  if (jevois::serial::flowhard::get()) options.c_cflag |= CRTSCTS;
190 
191  // Set all the options now:
192  if (tcsetattr(itsDev, TCSANOW, &options) == -1) SERTHROW("Failed to set port options");
193 
194  // We are operational:
195  itsErrno.store(0);
196  LINFO("Serial driver [" << instanceName() << "] ready on " << jevois::serial::devname::get());
197 }
198 
199 // ######################################################################
201 {
202  std::lock_guard<std::mutex> _(itsMtx);
203 
204  if (itsDev != -1)
205  {
206  if (tcsetattr(itsDev, TCSANOW, &itsSavedState) == -1) LERROR("Failed to restore serial port state -- IGNORED");
207  ::close(itsDev);
208  itsDev = -1;
209  }
210 }
211 
212 // ######################################################################
213 void jevois::Serial::setBlocking(bool blocking, std::chrono::milliseconds const & timeout)
214 {
215  std::lock_guard<std::mutex> _(itsMtx);
216 
217  int flags = fcntl(itsDev, F_GETFL, 0);
218  if (flags == -1) SERFATAL("Cannot get flags");
219  if (blocking) flags &= (~O_NONBLOCK); else flags |= O_NONBLOCK;
220  if (fcntl(itsDev, F_SETFL, flags) == -1) SERFATAL("Cannot set flags");
221 
222  // If blocking, set a timeout on the descriptor:
223  if (blocking)
224  {
225  termios options;
226  if (tcgetattr(itsDev, &options) == -1) SERFATAL("Failed to get options");
227  options.c_cc[VMIN] = 0;
228  options.c_cc[VTIME] = timeout.count() / 100; // vtime is in tenths of second
229  if (tcsetattr(itsDev, TCSANOW, &options) == -1) SERFATAL("Failed to set port options");
230  }
231 }
232 
233 // ######################################################################
234 void jevois::Serial::toggleDTR(std::chrono::milliseconds const & dur)
235 {
236  std::lock_guard<std::mutex> _(itsMtx);
237 
238  struct termios tty, old;
239 
240  if (tcgetattr(itsDev, &tty) == -1 || tcgetattr(itsDev, &old) == -1) SERFATAL("Failed to get attributes");
241 
242  cfsetospeed(&tty, B0);
243  cfsetispeed(&tty, B0);
244 
245  if (tcsetattr(itsDev, TCSANOW, &tty) == -1) SERFATAL("Failed to set attributes");
246 
247  std::this_thread::sleep_for(dur);
248 
249  if (tcsetattr(itsDev, TCSANOW, &old) == -1) SERFATAL("Failed to restore attributes");
250 }
251 
252 // ######################################################################
254 {
255  std::lock_guard<std::mutex> _(itsMtx);
256 
257  // Send a Hangup to the port
258  tcsendbreak(itsDev, 0);
259 }
260 
261 // ######################################################################
262 bool jevois::Serial::readSome(std::string & str)
263 {
264  if (itsErrno.load()) { tryReconnect(); if (itsErrno.load()) return false; }
265 
266  std::lock_guard<std::mutex> _(itsMtx);
267 
268  unsigned char c;
269 
270  while (true)
271  {
272  int n = ::read(itsDev, reinterpret_cast<char *>(&c), 1);
273 
274  if (n == -1)
275  {
276  if (errno == EAGAIN) return false; // no new char available
277  else SERFATAL("Read error");
278  }
279  else if (n == 0) return false; // no new char available
280 
282  {
283  case jevois::serial::LineStyle::LF:
284  if (c == '\n') { str = std::move(itsPartialString); itsPartialString.clear(); return true; }
285  else itsPartialString += c;
286  break;
287 
288  case jevois::serial::LineStyle::CR:
289  if (c == '\r') { str = std::move(itsPartialString); itsPartialString.clear(); return true; }
290  else itsPartialString += c;
291  break;
292 
293  case jevois::serial::LineStyle::CRLF:
294  if (c == '\n') { str = std::move(itsPartialString); itsPartialString.clear(); return true; }
295  else if (c != '\r') itsPartialString += c;
296  break;
297 
298  case jevois::serial::LineStyle::Zero:
299  if (c == 0x00) { str = std::move(itsPartialString); itsPartialString.clear(); return true; }
300  else itsPartialString += c;
301  break;
302 
303  case jevois::serial::LineStyle::Sloppy: // Return when we receive first separator, ignore others
304  if (c == '\r' || c == '\n' || c == 0x00 || c == 0xd0)
305  {
306  if (itsPartialString.empty() == false)
307  { str = std::move(itsPartialString); itsPartialString.clear(); return true; }
308  }
309  else itsPartialString += c;
310  break;
311  }
312  }
313 }
314 
315 // ######################################################################
316 void jevois::Serial::writeString(std::string const & str)
317 {
318  // If in error, silently drop all data until we successfully reconnect:
319  if (itsErrno.load()) { tryReconnect(); if (itsErrno.load()) return; }
320 
321  std::string fullstr(str);
322 
324  {
325  case jevois::serial::LineStyle::CR: fullstr += '\r'; break;
326  case jevois::serial::LineStyle::LF: fullstr += '\n'; break;
327  case jevois::serial::LineStyle::CRLF: fullstr += "\r\n"; break;
328  case jevois::serial::LineStyle::Zero: fullstr += '\0'; break;
329  case jevois::serial::LineStyle::Sloppy: fullstr += "\r\n"; break;
330  }
331 
332  std::lock_guard<std::mutex> _(itsMtx);
333  writeInternal(fullstr.c_str(), fullstr.length());
334 }
335 
336 // ######################################################################
337 void jevois::Serial::writeInternal(void const * buffer, const int nbytes, bool nodrop)
338 {
339  // Nodrop is used to prevent dropping even if the user wants it, e.g., during fileGet().
340  if (nodrop)
341  {
342  // Just write it all, never quit, never drop:
343  int ndone = 0; char const * b = reinterpret_cast<char const *>(buffer);
344  while (ndone < nbytes)
345  {
346  int n = ::write(itsDev, b + ndone, nbytes - ndone);
347  if (n == -1 && errno != EAGAIN) SERFATAL("Write error");
348 
349  // If we did not write the whole thing, the serial port is saturated, we need to wait a bit:
350  if (n > 0) ndone += n;
351  if (ndone < nbytes) tcdrain(itsDev); // on USB disconnect, this will hang forever...
352  }
353  }
354  else if (drop::get())
355  {
356  // Just write and silently drop (after a few attempts) if we could not write everything:
357  int ndone = 0; char const * b = reinterpret_cast<char const *>(buffer); int iter = 0;
358  while (ndone < nbytes && iter++ < 10)
359  {
360  int n = ::write(itsDev, b + ndone, nbytes - ndone);
361  if (n == -1 && errno != EAGAIN) SERFATAL("Write error");
362 
363  // If we did not write the whole thing, the serial port is saturated, we need to wait a bit:
364  if (n > 0) { ndone += n; iter = 0; }
365  if (ndone < nbytes) std::this_thread::sleep_for(std::chrono::milliseconds(5));
366  }
367  if (ndone < nbytes) SERFATAL("Timeout (host disconnect or overflow) -- SOME DATA LOST");
368  }
369  else
370  {
371  // Try to write a few times, then, if not done, report overflow and drop what remains:
372  int ndone = 0; char const * b = reinterpret_cast<char const *>(buffer); int iter = 0;
373  while (ndone < nbytes && iter++ < 50)
374  {
375  int n = ::write(itsDev, b + ndone, nbytes - ndone);
376  if (n == -1 && errno != EAGAIN) SERFATAL("Write error");
377 
378  // If we did not write the whole thing, the serial port is saturated, we need to wait a bit:
379  if (n > 0) ndone += n;
380  if (ndone < nbytes) std::this_thread::sleep_for(std::chrono::milliseconds(2));
381  }
382 
383  if (ndone < nbytes)
384  {
385  // If we had a serial overflow, we need to let the user know, but how, since the serial is overflowed already?
386  // Let's first throttle down big time, and then we throw:
387  std::this_thread::sleep_for(std::chrono::milliseconds(100));
388 
389  // Report the overflow once in a while:
390  ++itsWriteOverflowCounter; if (itsWriteOverflowCounter > 100) itsWriteOverflowCounter = 0;
391  if (itsWriteOverflowCounter == 1)
392  throw std::overflow_error("Serial write overflow: need to reduce amount ot serial writing");
393 
394  // Note how we are otherwise just ignoring the overflow and hence dropping data.
395  }
396  else itsWriteOverflowCounter = 0;
397  }
398 }
399 
400 // ######################################################################
402 {
403  std::lock_guard<std::mutex> _(itsMtx);
404 
405  // Flush the input
406  if (tcflush(itsDev, TCIFLUSH) != 0) LDEBUG("Serial flush error -- IGNORED");
407 }
408 
409 
410 // ######################################################################
412 { }
413 
414 // ####################################################################################################
416 { return itsType; }
417 
418 // ####################################################################################################
419 void jevois::Serial::fileGet(std::string const & abspath)
420 {
421  std::lock_guard<std::mutex> _(itsMtx);
422 
423  std::ifstream fil(abspath, std::ios::in | std::ios::binary);
424  if (fil.is_open() == false) throw std::runtime_error("Could not read file " + abspath);
425 
426  // Get file length and send it out in ASCII:
427  fil.seekg(0, fil.end); size_t num = fil.tellg(); fil.seekg(0, fil.beg);
428 
429  std::string startstr = "JEVOIS_FILEGET " + std::to_string(num) + '\n';
430  writeInternal(startstr.c_str(), startstr.length(), true);
431 
432  // Read blocks and send them to serial:
433  size_t const bufsiz = std::min(num, size_t(1024 * 1024)); char buffer[1024 * 1024];
434  while (num)
435  {
436  size_t got = std::min(bufsiz, num); fil.read(buffer, got); if (!fil) got = fil.gcount();
437  writeInternal(buffer, got, true);
438  num -= got;
439  }
440 }
441 
442 // ####################################################################################################
443 void jevois::Serial::filePut(std::string const & abspath)
444 {
445  std::ofstream fil(abspath, std::ios::out | std::ios::binary);
446  if (fil.is_open() == false) throw std::runtime_error("Could not write file " + abspath);
447 
448  // Get file length as ASCII:
449  std::string lenstr; int timeout = 1000;
450  while (readSome(lenstr) == false)
451  {
452  std::this_thread::sleep_for(std::chrono::milliseconds(2));
453  --timeout;
454  if (timeout == 0) throw std::runtime_error("Timeout waiting for file length for " + abspath);
455  }
456 
457  if (jevois::stringStartsWith(lenstr, "JEVOIS_FILEPUT ") == false)
458  throw std::runtime_error("Incorrect header while receiving file " + abspath);
459 
460  auto vec = jevois::split(lenstr);
461  if (vec.size() != 2) throw std::runtime_error("Incorrect header fields while receiving file " + abspath);
462 
463  size_t num = std::stoul(vec[1]);
464 
465  // Read blocks from serial and write them to file:
466  std::lock_guard<std::mutex> _(itsMtx);
467  size_t const bufsiz = std::min(num, size_t(1024 * 1024)); char buffer[1024 * 1024];
468  while (num)
469  {
470  int got = ::read(itsDev, buffer, bufsiz);
471  if (got == -1 && errno != EAGAIN) throw std::runtime_error("Serial: Read error");
472 
473  if (got > 0)
474  {
475  fil.write(buffer, got);
476  num -= got;
477  }
478  else std::this_thread::sleep_for(std::chrono::milliseconds(2));
479  }
480  fil.close();
481 }
jevois::Component::instanceName
const std::string & instanceName() const
The instance name of this component.
Definition: Component.C:50
jevois::imu::get
Data collection mode RAW means that the latest available raw data is returned each time get() is called
jevois::Serial::setBlocking
void setBlocking(bool blocking, std::chrono::milliseconds const &timeout)
Set the access to blocking or not.
Definition: Serial.C:213
jevois::Serial::sendBreak
void sendBreak(void)
transmit continuous stream of zero-valued bits for specific duration.
Definition: Serial.C:253
LDEBUG
#define LDEBUG(msg)
Convenience macro for users to print out console or syslog messages, DEBUG level.
Definition: Log.H:173
jevois::split
std::vector< std::string > split(std::string const &input, std::string const &regex="\\s+")
Split string into vector of tokens using a regex to specify what to split on; default regex splits by...
Definition: Utils.C:270
jevois::Serial::Serial
Serial(std::string const &instance, UserInterface::Type type)
Constructor.
Definition: Serial.C:61
SERTHROW
#define SERTHROW(msg)
Definition: Serial.C:34
jevois::Serial::readSome
bool readSome(std::string &str) override
Read some bytes if available, and return true and a string when one is complete.
Definition: Serial.C:262
jevois::UserInterface
Abstract base class for a string-based user interface.
Definition: UserInterface.H:32
jevois::Serial::postInit
void postInit() override
Called after all sub-Components are init()ed.
Definition: Serial.C:66
LERROR
#define LERROR(msg)
Convenience macro for users to print out console or syslog messages, ERROR level.
Definition: Log.H:211
jevois::Serial::filePut
void filePut(std::string const &abspath)
Receive a file from the host and write it to the local microSD.
Definition: Serial.C:443
jevois::Serial::toggleDTR
void toggleDTR(std::chrono::milliseconds const &dur)
Set the DTR mode off momentarily.
Definition: Serial.C:234
jevois
Definition: Concepts.dox:1
Engine.H
Serial.H
jevois::Serial::writeString
void writeString(std::string const &str) override
Write a string, using the line termination convention of serial::linestyle.
Definition: Serial.C:316
jevois::Serial::flush
void flush(void)
Flush all inputs.
Definition: Serial.C:401
jevois::Serial::~Serial
virtual ~Serial()
destructor
Definition: Serial.C:411
jevois::Engine::reportError
void reportError(std::string const &err)
Definition: Engine.C:1367
jevois::UserInterface::Type
Type
Enum for the interface type.
Definition: UserInterface.H:59
jevois::stringStartsWith
bool stringStartsWith(std::string const &str, std::string const &prefix)
Return true if str starts with prefix (including if both strings are equal)
Definition: Utils.C:294
SERFATAL
#define SERFATAL(msg)
Definition: Serial.C:29
jevois::to_string
std::string to_string(T const &val)
Convert from type to string.
jevois::Component::engine
Engine * engine() const
Get a handle to our Engine, or throw if we do not have an Engine as root ancestor.
Definition: Component.C:129
jevois::Serial::postUninit
void postUninit() override
Called after all sub-Components are uninit()ed.
Definition: Serial.C:200
jevois::async_little
std::future< std::invoke_result_t< std::decay_t< Function >, std::decay_t< Args >... > > async_little(Function &&f, Args &&... args)
Async execution using a thread pool.
jevois::imu::rate
Magnetometer sampling or Off to disable or Once to only get one measurement You can repeatedly set this parameter to Once to obtain repeated measurements at your own pace In JeVois you need to alternate between Off and Once In FIFO grate controls the data rate
Definition: ICM20948.H:115
jevois::Serial::type
UserInterface::Type type() const override
Return our port type, here Hard or USB.
Definition: Serial.C:415
LINFO
#define LINFO(msg)
Convenience macro for users to print out console or syslog messages, INFO level.
Definition: Log.H:194
jevois::Serial::fileGet
void fileGet(std::string const &abspath)
Send a file from the local microSD to the host computer.
Definition: Serial.C:419