How to check if serial port is already open (by another process) in Linux, using Python 2.7 (and possibly pyserial)?

StackOverflow https://stackoverflow.com/questions/19809867

Question

I know there are other questions quite similar to mine, but none of them address the issue I'm having.

I'd like to use pyserial to access a serial port (/dev/tty...), but only on the condition that another process hasn't already opened it.

The following snippet returns four available ports on my Ubuntu 12.04 machine, when run once. If I run it a second time I would expect no ports to be available. Sadly, the same list of ports are returned. It seems pyserial cannot identify that another process has already opened the port.

I'd expect a SerialException to be thrown, or the isOpen() method to return False, but pyserial happily opens the multiple times.

import serial
from serial import tools
from serial.tools import list_ports


def available_ttys():
    for tty in serial.tools.list_ports.comports():
        try:
            port = serial.Serial(port=tty[0])
            if port.isOpen():
                yield port
        except serial.SerialException as ex:
            print 'Port {0} is unavailable: {1}'.format(tty, ex)


def main():
    ttys = []
    for tty in available_ttys():
        ttys.append(tty)
        print tty

    input('waiting ...')


if __name__ == '__main__':
    main()

This is the output regardless of how many times I run it in parallel:

Port ('/dev/ttyS31', 'ttyS31', 'n/a') is unavailable: Could not configure port: (5, 'Input/output error')
...
Port ('/dev/ttyS0', 'ttyS0', 'n/a') is unavailable: Could not configure port: (5, 'Input/output error')
Serial<id=0x7fca9d9f1c90, open=True>(port='/dev/ttyUSB1', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=None, xonxoff=False, rtscts=False, dsrdtr=False)
Serial<id=0x7fca9d9f1cd0, open=True>(port='/dev/ttyACM2', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=None, xonxoff=False, rtscts=False, dsrdtr=False)
Serial<id=0x7fca9d9f1e50, open=True>(port='/dev/ttyACM1', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=None, xonxoff=False, rtscts=False, dsrdtr=False)
Serial<id=0x7fca9d9f1ed0, open=True>(port='/dev/ttyACM0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=None, xonxoff=False, rtscts=False, dsrdtr=False)
waiting ...
Was it helpful?

Solution

As @VooDooNOFX said, there's no technical limitation on preventing the other processes from opening the same port (device). However, on Linux you can lock the file to prevent your application from using the same port multiple times.

import fcntl, serial

s = serial.Serial(0)
fcntl.flock(s.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)

In this case, your application will try to obtain an exclusive lock (LOCK_EX) on the serial port. Thanks to LOCK_NB, the call will fail immediately if any other process has locked the serial port already — through raising IOError (or BlockingIOError sub-exception in Python 3.3).

This has two advantages over the other solution:

  1. You are not introducing any non-standard files but use system-provided method which brings better interoperability,
  2. The lock is immediately released when your process exits, so you don't have to worry about stale locks.

So, your function would look like:

def available_ttys():
    for tty in serial.tools.list_ports.comports():
        try:
            port = serial.Serial(port=tty[0])
            if port.isOpen():
                try:
                    fcntl.flock(port.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
                except IOError:
                    print 'Port {0} is busy'.format(tty)
                else:
                    yield port
        except serial.SerialException as ex:
            print 'Port {0} is unavailable: {1}'.format(tty, ex)

OTHER TIPS

There is nothing in Linux which prevents multiple processes from opening the same serial port. Hence why the pyserial library is capable of doing this. There is however a standard-ish convention which has been well documented elsewhere: https://superuser.com/questions/488908/sharing-a-serial-port-between-two-processes

The generic process requires you to open the device, then create a text file in /tmp or /var/lock directories which contains your PID. The second script will search for the existence of this file, and refuse to open the port of it exists.

For much more information, see: http://www.tldp.org/HOWTO/Serial-HOWTO-13.html

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top