17

I am using the termios api in Linux to communicate with a serial device. I'm trying to detect if the device has disconnected so I can try to reconnect after some timeout. I have the following example code:

while(1)
{
    FD_ZERO(&rfds);
    FD_SET(tty_fd, &rfds);

    // have tried checking fcntl(tty_fd, F_GETFL); too

    // Blocking call to wait until we have data
    select(tty_fd+1, &rfds, NULL, NULL, NULL);

    // While we have data, collect it
    while (read(tty_fd, &c, 1)>0 && bytesRead++<200)
    {
        serialBuffer.push_back(c);
    }

    bytesRead = 0;

    // Try to parse it
    BufferParse();
}

I'm not actually seeing select() or fcntl return error values (-1) after the ttyUSB device is physically disconnected. I could, of course, check to see if the file in /dev/ exists, but I was hoping there was a more elegant solution.

Would appreciate any advice, thanks!

chris12892
  • 1,634
  • 2
  • 18
  • 36
  • 3
    With a USB adapter, there could be two levels of disconnection. With a device connected to the serial port, that device could no longer communicating. There could be a disconnect of the RS232 link itself. The DSR/DTR signal is often used to determine the local connection. With USB also in the picture, the USB adapter can become disconnected from the host. So you need to clarify what you're trying to detect. Reading data would originate from the attached serial device, not the serial port or the USB adapter. If the device is a modem, then you have another connection link. – sawdust Dec 10 '15 at 22:14
  • 1
    I'm specifically talking about the USB to serial device being disconnected from the host machine in this case. – chris12892 Dec 11 '15 at 06:38
  • 1
    You may use libudev to watch device events through a file descriptor: http://www.signal11.us/oss/udev/ – Mathieu Dec 11 '15 at 08:39
  • I think you should also set `exceptfds` (parameter #4) for `select()`. You'll not get -1 for `select()` as long as the socket itself is not closed (and it can only get closed by your program). You probably _should_ get an indication for `readfds` as well (beyond `exceptfds`), and when trying to `read()`, you should get an error there as well (EIO, or similar). – Laszlo Valko Dec 11 '15 at 17:21
  • How about `fstat()`? Do you get an `EBADF` on the `tty_fd` if fstat-ed after the device is removed?... – TheCodeArtist Dec 12 '15 at 16:43
  • Nothing from the exceptfds param, read does not return a -1, and fstat() does not return a -1 either. – chris12892 Dec 12 '15 at 17:36
  • Something else: the value of errno never changes regardless of whethere connected or not. It stays at 11 – chris12892 Dec 12 '15 at 17:55
  • @chris12892 errno from what? EAGAIN=11. Can you update code to show how your testing the return value of select() and add an exceptfds set please? – spinkus Dec 15 '15 at 08:17
  • 5
    As sawdust posted, there are many types of disconnection. I had a case in the past I could not determine if the serial link was lost due to simple causes like a broken cable. I end up using a serial heartbeat and detecting it on a timeout thread. This is the safer solution as there may have situations where termios will not signal. – Mendes Dec 16 '15 at 02:58

1 Answers1

5

First of all it worth mentioning, that the behavior serial-usb is following:

On usb device unplugged disconnect is called

@disconnect: Called when the interface is no longer accessible, usually because its device has been (or is being) disconnected or the driver module is being unloaded.

in our case it is usb_serial_disconnect(struct usb_interface *interface)

which calles usb_serial_console_disconnect(serial), which calles tty_hangup ... and so on.

You can follow chain started from here: http://lxr.free-electrons.com/source/drivers/usb/serial/usb-serial.c#L1091

In short this results in following classic manner:

pselect signals that file descriptor is ready and ioctl(fd, FIONREAD, &len) returns zero len.

That's it you unplugged the device.

Summurizing (derived from your code) :

while(1)
{
    FD_ZERO(&rfds);
    FD_SET(tty_fd, &rfds);

    // have tried checking fcntl(tty_fd, F_GETFL); too

    // Blocking call to wait until we have data
    int ready = select(tty_fd + 1, &rfds, NULL, NULL, NULL);

    if(ready && FD_ISSET(tty_fd, &rfds)) {
      size_t len = 0;
      ioctl(tty_fd, FIONREAD, &len);
      errsv = errno;

      if(len == 0)
      {
         printf("prog_name: zero read from the device: %s.", strerror(errsv));
         /* close fd and cleanup or reconnect etc...*/
         exit(EXIT_FAILURE);
      }

      // While we have data, collect it
      while (read(tty_fd, &c, 1)>0 && bytesRead++<200)
      {
        serialBuffer.push_back(c);
      }

      bytesRead = 0;

      // Try to parse it
      BufferParse();
    }
}

It's a pity that you did not say what kind of device you are using.

In case if your device is capable of RTS/CTS flow control it is also possbile to detect line break.

Maquefel
  • 480
  • 4
  • 16
  • Hey, thanks for the detailed comment! The device is an FTDI chip attached to a Raspberry Pi. Raspian Jessie, if the distro matters. I'll try this a little later and update how it worked out. – chris12892 Jan 24 '16 at 02:28
  • Np, actually zero read comes from replacing default ops handlers with dummy ones, which return zero on read. It is pretty much same behavior as sockets. – Maquefel Jan 24 '16 at 08:44