Robust Linux Python serial port filtering by name and manufacturer using udev
udev_info_example.py
{
'serial': '01010A23535223934CF29A1EF5000007',
'vendor_id': '1d50',
'usb_model': 'Marlin USB Device',
'usb_model_id': '6029',
'usb_vendor':
'marlinfw.org',
'vendor': 'OpenMoko, Inc.',
'model': 'Marlin 2.0 (Serial)'
}
```
or just go to straight hard-coding the port such as `/dev/ttyACM0`.
It should be quite obvious why this isn't robust:
* There might be multiple serial ports and the order is unknown
* You code might need to work on different computers, which lead to different ports being assigned
* When you re-plug a device, it might get assigned a differnent serial port number (e.g. `/dev/ttyACM1`)
The following code uses only the linux integrated tool `udevadm` to query information and built-in libraries. **Currently it does not work on anything but Linux.**
```python {filename="udev_serial_utils.py"}
import glob
import subprocess
import re
def get_serial_ports():
# Get a list of /dev/ttyACM* and /dev/ttyUSB* devices
ports = glob.glob('/dev/ttyACM*') + glob.glob('/dev/ttyUSB*')
return ports
def get_udev_info(port):
# Run the udevadm command and get the output
result = subprocess.run(['udevadm', 'info', '-q', 'all', '-r', '-n', port], capture_output=True, text=True)
return result.stdout
def find_serial_ports(vendor=None, model=None, usb_vendor=None, usb_model=None, usb_model_id=None, vendor_id=None):
# Mapping of human-readable names to udevadm keys
attribute_mapping = {
"vendor": "ID_VENDOR_FROM_DATABASE",
"model": "ID_MODEL_FROM_DATABASE",
"usb_vendor": "ID_USB_VENDOR",
"usb_model": "ID_USB_MODEL_ENC",
"usb_model_id": "ID_USB_MODEL_ID",
"vendor_id": "ID_VENDOR_ID",
"usb_path": "ID_PATH",
"serial": "ID_SERIAL_SHORT"
}
# Filters based on provided arguments
filters = {}
for key, value in locals().items():
if value and key in attribute_mapping:
filters[attribute_mapping[key]] = value
# Find and filter ports
ports = get_serial_ports()
filtered_ports = []
for port in ports:
udev_info = get_udev_info(port)
match = True
for key, value in filters.items():
if not re.search(f"{key}={re.escape(value)}", udev_info):
match = False
break
if match:
filtered_ports.append(port)
return filtered_ports
def get_serial_port_info(port):
# Mapping of udevadm keys to human-readable names
attribute_mapping = {
"ID_VENDOR_FROM_DATABASE": "vendor",
"ID_MODEL_FROM_DATABASE": "model",
"ID_USB_VENDOR": "usb_vendor",
"ID_USB_MODEL_ENC": "usb_model",
"ID_USB_MODEL_ID": "usb_model_id",
"ID_VENDOR_ID": "vendor_id",
"ID_SERIAL_SHORT": "serial",
}
# Run the udevadm command and get the output
udev_info = get_udev_info(port)
port_info = {}
for line in udev_info.splitlines():
if line.startswith('E: '):
key, value = line[3:].split('=', 1)
if key in attribute_mapping:
# Decode escape sequences like \x20 to a space.
# NOTE: Since only \x20 is common, we currently only replace that one
port_info[attribute_mapping[key]] = value.replace('\\x20', ' ')
return port_info
def find_serial_port(**kwargs):
"""
Find a single serial port matching the provided filters.
"""
ports = find_serial_ports(**kwargs)
if len(ports) > 1:
raise ValueError("Multiple matching ports found for filters: " + str(kwargs))
elif len(ports) == 0:
raise ValueError("No matching ports found for filters: " + str(kwargs))
else:
return ports[0]
Example: Find a serial port
find_serial_example.py
# Example usage: Find a serial port
matching_ports = find_serial_ports(vendor="OpenMoko, Inc.")
print(matching_ports) # Prints e.g. ['/dev/ttyACM0']
Example: Print information about a given port
This is typically used so you know what filters to add for find_serial_ports()
.
print_port_info_example.py
# Example usage: Print info about a serial port
serial_port_info = get_serial_port_info('/dev/ttyACM0')
print(serial_port_info)
This prints, for example:
example.py
{
'serial': '01010A23535223934CF29A1EF5000007',
'vendor_id': '1d50',
'usb_model': 'Marlin USB Device',
'usb_model_id': '6029',
'usb_vendor':
'marlinfw.org',
'vendor': 'OpenMoko, Inc.',
'model': 'Marlin 2.0 (Serial)'
}
Check out similar posts by category:
Electronics, Linux
If this post helped you, please consider buying me a coffee or donating via PayPal to support research & publishing of new posts on TechOverflow