Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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 ...
like image 428
Micke Avatar asked Nov 06 '13 10:11

Micke


People also ask

How do I know if a serial port is open in python?

isOpen() # try to open port, if possible print message and proceed with 'while True:' print ("port is opened!") except IOError: # if port is already opened, close it and open it again and print message ser. close() ser. open() print ("port was already open, was closed and opened again!") while True: # do something...

Does pySerial work on Linux?

pySerial 1.21 is compatible with Python 2.0 on Windows, Linux and several un*x like systems, MacOSX and Jython.


1 Answers

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)
like image 105
Michał Górny Avatar answered Nov 09 '22 04:11

Michał Górny