psi46 / elComandante

1 stars 5 forks source link

Bug with udev symlink and pySerial #14

Open xerxes1986 opened 9 years ago

xerxes1986 commented 9 years ago

I have finally found a udev rule that assigns a consistent link to the coldbox (instead of the random /dev/ttyUSB#) which is:

ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", GROUP="usb", MODE="0664", SYMLINK+="ttyUSBColdbox"

The problem is that this symlink creates a link to the wrong thing. For example, the coldbox is /dev/ttyUSB0 but the symlink /dev/ttyUSBColdbox points to bus/usb/003/005 instead of /dev/ttyUSB0. When trying to run the coldbox client, it gives the following error:

fnalpix2@fnalpix2-Inspiron-660:~/elComandante/coolingBox$ ./coolingBoxClient.py -d /dev/ttyUSBColdbox 
    |  CoolingBoxLog: Set Logfile to "./CoolingBox.log"
SerialPort: /dev/ttyUSBColdbox
Could not initialize Jumo. Try again: 1/10, Could not configure port: (25, 'Inappropriate ioctl for device')
Exception AttributeError: "jumo_coolingBox instance has no attribute 'controlling'" in <bound method jumo_coolingBox.__del__ of <jumo_coolingBox.jumo_coolingBox instance at 0x2600950>> ignored

The solution I've found for now is to use the automatically created symlinks in /dev/serial/by-id/:

fnalpix2@fnalpix2-Inspiron-660:~/elComandante/coolingBox$ ls -alh /dev/serial/by-id/usb-FTDI_USB__-__Serial-if00-port0 
lrwxrwxrwx 1 root root 13 Jun  9 15:43 /dev/serial/by-id/usb-FTDI_USB__-__Serial-if00-port0 -> ../../ttyUSB0

I think these symlinks will work, but I was hoping the symlink option in udev would work as it seems like it would be more consistent across platforms. Has anyone gotten this to work?

veloxid commented 9 years ago

Hi Paul,

On RHEL we use the following udev rule which is working perfectly for us:

SUBSYSTEM == "tty", ATTRS{manufacturer} == "FTDI", ATTRS{product} == "USB <-> Serial", SYMLINK = "ttyJUMO", GROUP = "usb", MODE = "0664" SUBSYSTEM == "usb", ATTRS{manufacturer} == "FTDI", ATTRS{product} == "USB <-> Serial", GROUP = "usb", MODE = "0664"

I think we also managed to get udev rules working on ubuntu, i will look for them in the next days.

xerxes1986 commented 9 years ago

Hi Felix,

The way I understand udev is that any device matching the list of rules in a udev file (and the first match) will trigger the event, so your second line there doesn't matter. The first line doesn't work on our computers and I think the reason is the "SUBSYSTEM == "tty"" part. If I run udevadm info -n /dev/ttyUSB0 I get:

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

  looking at device
'/devices/pci0000:00/0000:00:14.0/usb3/3-2/3-2:1.0/ttyUSB0/tty/ttyUSB0':
    KERNEL=="ttyUSB0"
    SUBSYSTEM=="tty"
    DRIVER==""

  looking at parent device
'/devices/pci0000:00/0000:00:14.0/usb3/3-2/3-2:1.0/ttyUSB0':
    KERNELS=="ttyUSB0"
    SUBSYSTEMS=="usb-serial"
    DRIVERS=="ftdi_sio"
    ATTRS{port_number}=="0"
    ATTRS{latency_timer}=="1"

  looking at parent device
'/devices/pci0000:00/0000:00:14.0/usb3/3-2/3-2:1.0':
    KERNELS=="3-2:1.0"
    SUBSYSTEMS=="usb"
    DRIVERS=="ftdi_sio"
    ATTRS{bInterfaceClass}=="ff"
    ATTRS{bInterfaceSubClass}=="ff"
    ATTRS{bInterfaceProtocol}=="ff"
    ATTRS{bNumEndpoints}=="02"
    ATTRS{supports_autosuspend}=="1"
    ATTRS{bAlternateSetting}==" 0"
    ATTRS{bInterfaceNumber}=="00"
    ATTRS{interface}=="USB <-> Serial"

  looking at parent device '/devices/pci0000:00/0000:00:14.0/usb3/3-2':
    KERNELS=="3-2"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{devpath}=="2"
    ATTRS{idVendor}=="0403"
    ATTRS{speed}=="12"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bMaxPacketSize0}=="8"
    ATTRS{busnum}=="3"
    ATTRS{devnum}=="5"
    ATTRS{configuration}==""
    ATTRS{bMaxPower}=="90mA"
    ATTRS{authorized}=="1"
    ATTRS{bmAttributes}=="80"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{maxchild}=="0"
    ATTRS{bcdDevice}=="0400"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{version}==" 1.10"
    ATTRS{urbnum}=="6835546"
    ATTRS{ltm_capable}=="no"
    ATTRS{manufacturer}=="FTDI"
    ATTRS{removable}=="removable"
    ATTRS{idProduct}=="6001"
    ATTRS{bDeviceClass}=="00"
    ATTRS{product}=="USB <-> Serial"

  looking at parent device '/devices/pci0000:00/0000:00:14.0/usb3':
    KERNELS=="usb3"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="01"
    ATTRS{devpath}=="0"
    ATTRS{idVendor}=="1d6b"
    ATTRS{speed}=="480"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{authorized_default}=="1"
    ATTRS{busnum}=="3"
    ATTRS{devnum}=="1"
    ATTRS{configuration}==""
    ATTRS{bMaxPower}=="0mA"
    ATTRS{authorized}=="1"
    ATTRS{bmAttributes}=="e0"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{maxchild}=="4"
    ATTRS{bcdDevice}=="0313"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{serial}=="0000:00:14.0"
    ATTRS{version}==" 2.00"
    ATTRS{urbnum}=="70"
    ATTRS{ltm_capable}=="no"
    ATTRS{manufacturer}=="Linux 3.13.0-53-generic xhci_hcd"
    ATTRS{removable}=="unknown"
    ATTRS{idProduct}=="0002"
    ATTRS{bDeviceClass}=="09"
    ATTRS{product}=="xHCI Host Controller"

  looking at parent device '/devices/pci0000:00/0000:00:14.0':
    KERNELS=="0000:00:14.0"
    SUBSYSTEMS=="pci"
    DRIVERS=="xhci_hcd"
    ATTRS{irq}=="43"
    ATTRS{subsystem_vendor}=="0x1028"
    ATTRS{broken_parity_status}=="0"
    ATTRS{class}=="0x0c0330"
    ATTRS{consistent_dma_mask_bits}=="64"
    ATTRS{dma_mask_bits}=="64"

ATTRS{local_cpus}=="00000000,00000000,00000000,00000000,00000000,00000000,00000000,0000000f"
    ATTRS{device}=="0x1e31"
    ATTRS{enable}=="1"
    ATTRS{msi_bus}==""
    ATTRS{local_cpulist}=="0-3"
    ATTRS{vendor}=="0x8086"
    ATTRS{subsystem_device}=="0x0581"
    ATTRS{numa_node}=="-1"
    ATTRS{d3cold_allowed}=="1"

  looking at parent device '/devices/pci0000:00':
    KERNELS=="pci0000:00"
    SUBSYSTEMS==""
    DRIVERS==""

The way the rules are written, you should be able to use a combination of exactly one device and one of that device's parents attributes. However, this doesn't work for whatever reason on Ubuntu so for the rule to work I need to remove the SUBSYSTEM == "tty" part, but I don't see why this would screw up the symlink since this is a matching rule not an action rule.

I did however find a solution by changing "SYMLINK" to "NAME", which creates the virtual serial port with the assigned name. I'm not sure if this will have any issues in the future though.

Paul Turner

On Wed, Jun 10, 2015 at 3:12 AM, Felix Bachmair notifications@github.com wrote:

Hi Paul,

On RHEL we use the following udev rule which is working perfectly for us:

SUBSYSTEM == "tty", ATTRS{manufacturer} == "FTDI", ATTRS{product} == "USB <-> Serial", SYMLINK = "ttyJUMO", GROUP = "usb", MODE = "0664" SUBSYSTEM == "usb", ATTRS{manufacturer} == "FTDI", ATTRS{product} == "USB <-> Serial", GROUP = "usb", MODE = "0664"

— Reply to this email directly or view it on GitHub https://github.com/psi46/elComandante/issues/14#issuecomment-110645584.

cfangmeier commented 8 years ago

Since this issue is still open, I would like to suggest an approach to this problem that sidesteps the udev configuration step by instead handling this in code. This uses the pyudev package to interact with udev at runtime to identify which connected device is the Keithley, which is the JUMO.

#!/usr/bin/env python2
from __future__ import print_function
import pyudev

context = pyudev.Context()

def device_by_id(id_vendor, id_product):
    match_devices = []
    for dev in context.list_devices(subsystem='tty'):
        try:
            ancestor = dev.parent.parent.parent
            attribs = ancestor.attributes
        except AttributeError:
                continue
        try:
            idVendor = attribs['idVendor']
            idProduct = attribs['idProduct']
        except KeyError:
            continue
        if idVendor == id_vendor and idProduct == id_product:
            match_devices.append(dev)
    return match_devices

def find_single_device(id_vendor, id_product):
    devs = device_by_id(id_vendor, id_product)
    if len(devs) == 0:
        msg = "Unable to find device with idVendor={} and idProduct={}"
        raise ValueError(msg.format(id_vendor, id_product))
    elif len(devs) > 1:
        msg = "Found multiple devices with idVendor={} and idProduct={}"
        raise ValueError(msg.format(id_vendor, id_product))
    else:
        return devs[0].device_node

keithley_dev = find_single_device('06cd', '0121')
print("Found Keithley on port", keithley_dev)

jumo_dev = find_single_device('0403', '6001')
print("Found JUMO on port", jumo_dev)

Which, on our system, gives the output:

Found Keithley on port /dev/ttyUSB2
Found JUMO on port /dev/ttyUSB0

Now, a problem that this doesn't solve is if the devices get mounted with incorrect permissions. For this, a UDEV rule would likely still be required. However, I'm not sure if this is a problem that many people run into.

I also cannot vouch for how platform independent this is. For example, it's possible that different platforms will handle the device nesting differently, so the line dev.parent.parent.parent would have to be modified.

dshipovsky commented 3 years ago

Found answer @ https://www.indilib.org/forum/mounts/3757-port-failure-error-inappropriate-ioctl-for-device.html Thanks to ADSF. "Just FYI for the people googling this in the future: I had this exact same problem with a CP201x usb-serial adapter. The solution is to change all the "ATTR" references to "ATTRS" in the udev rules. That's it. That's the magic incantation. I hoped this helped someone. In this case changing ATTR{manufacturer}=="Prolific Technology Inc." to ATTRS{manufacturer}=="Prolific Technology Inc." most likely would have fixed OP's issue."

Works for me (py3.8, linux kernel 3.14): ACTION=="add",ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001",SYMLINK+="ttyUSB_ftdi"

theballmarcus commented 8 months ago

Just for future people using cfangmeiers answer. The code they wrote works if you update device_by_id like this:

def device_by_id(id_vendor, id_product):
    match_devices = []
    for dev in context.list_devices():
        try:
            if(dev.properties['ID_VENDOR_ID'] == id_vendor and dev.properties['ID_MODEL_ID'] == id_product):
                match_devices.append(dev)
        except KeyError:
                continue
    return match_devices