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 ...
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...
pySerial 1.21 is compatible with Python 2.0 on Windows, Linux and several un*x like systems, MacOSX and Jython.
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:
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With