pyserial перечисляет неправильные последовательные порты на Ubuntu 24.04

Вопрос или проблема

Недавно я обновил несколько ПК с Ubuntu 22.04 до Ubuntu 24.04. Я использую несколько python-скриптов для чтения/записи в последовательные порты.

Странно, но когда я перечисляю доступные последовательные порты с помощью serial.tools.list_ports.comports() на Ubuntu 24.04, он также перечисляет все ненастоящие устройства /dev/ttyS* (0-31), которые отсутствуют на моем оборудовании.

На тех же машинах с Ubuntu 22.04 перечисляются только существующие последовательные порты.

>>> from serial.tools.list_ports import comports
>>> [p.name for p in comports()]

Вывод на Ubuntu 24.04:

['ttyS0', 'ttyS1', ..., 'ttyS30', 'ttyS31', 'ttyUSB0', ..., 'ttyUSB7']

Вывод на Ubuntu 22.04:

['ttyUSB0', ..., 'ttyUSB7']

Когда я выполняю ls /dev на обеих версиях Ubuntu, обе перечисляют /dev/ttyS0 до /dev/ttyS31, но pyserial ведет себя по-разному.

Я использую версию pyserial 3.5.

Кто-нибудь знает, почему это происходит и как это исправить?

Вот что я узнал до сих пор:

Версия Ubuntu Версия ядра
22.04 6.2.0-26-generic
24.04 6.8.0-36-generic

По умолчанию, pyserial фильтрует устройства с подсистемой platform (есть другая дискуссия по этому поводу, например, этот пулл-реквест).

Тем не менее, Ubuntu 24.04 (или, точнее, более новые версии ядра) сообщают о другой подсистеме serial-base для всех (существующих и несуществующих) устаревших последовательных портов:

Тип порта Подсистема на Ubuntu 22.04 Подсистема на Ubuntu 24.04
Несуществующие порты (/dev/ttyS*) platform serial-base
Существующие устаревшие порты (/dev/ttyS*) pnp serial-base

Исходный код comports():
https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports_linux.py

def comports(include_links=False):
    devices = set()
    devices.update(glob.glob('/dev/ttyS*'))     # встроенные последовательные порты
    devices.update(glob.glob('/dev/ttyUSB*'))   # usb-serial с собственным драйвером
    devices.update(glob.glob('/dev/ttyXRUSB*')) # xr-usb-serial порт exar (DELL Edge 3001)
    devices.update(glob.glob('/dev/ttyACM*'))   # usb-serial с профилем CDC-ACM
    devices.update(glob.glob('/dev/ttyAMA*'))   # внутренний порт ARM (raspi)
    devices.update(glob.glob('/dev/rfcomm*'))   # Bluetooth последовательные устройства
    devices.update(glob.glob('/dev/ttyAP*'))    # Многопортовые последовательные контроллеры Advantech
    devices.update(glob.glob('/dev/ttyGS*'))    # https://www.kernel.org/doc/Documentation/usb/gadget_serial.txt

    if include_links:
        devices.update(list_ports_common.list_links(devices))
    return [info
            for info in [SysFS(d) for d in devices]
            if info.subsystem != "platform"]    # скрыть отсутствующие внутренние последовательные порты

Зная это, я мог бы потенциально вручную отфильтровать устройства с подсистемой serial-base в своем программном обеспечении, но это также исключит существующие устаревшие последовательные порты, что не то, что я хочу.

Я тоже недавно столкнулся с этой проблемой – код, который я использую на протяжении многих лет (основан на работе Сёрен Холма), проверял, чтобы драйвер устройства последовательного порта назывался “serial8250”. Если имя сопоставлялось, то последовательный порт открывался, и вызывался ioctl/TIOCGSERIAL для получения “типа” UART; если “тип” был ненулевым, то последовательный порт считался реальным. Если имя драйвера устройства было чем-то иным, чем “serial8250”, то последовательный порт считался реальным без необходимости какой-либо проверки.

Некоторое время между ядром 6.3 и ядром 6.8 имя для портов ttyS* было изменено с “serial8250” на просто “port”. Это сломало вышеуказанный алгоритм проверки, так как имя “port” стало слишком общим, чтобы на него полагаться. Я задал вопрос об этом здесь:
изменения в имени драйвера последовательного порта с ядром 6.8 и позже

Я работал над альтернативным тестом. Он написан на Pascal (FPC), хотя вы должны быть в состоянии перевести его на python без особых трудностей:

program project1;
uses SysUtils, BaseUnix, TermIO;

var DeviceName:string;
            SR:TSearchRec;
            FD:longint;
          tios:TermIOS;
      n, c, PT:integer;

begin
  n:=0;
  c:=0;
  if FindFirst('/dev/tty*', faAnyFile , SR) = 0 then
  repeat
    DeviceName:=SR.Name;

    if (DeviceName<>'.') and (DeviceName<>'..') then                           // исключить '.' и '..'
    if fpAccess('/dev/'+DeviceName, R_OK+W_OK)=0 then                          // исключить устройства, к которым у нас НЕТ ДОСТУПА
    if FileExists('/sys/class/tty/'+DeviceName+'/device/driver')  or           // это достаточно с FPC до 3.20
       DirectoryExists('/sys/class/tty/'+DeviceName+'/device/driver')  then    // с FPC 3.20 и позже нам нужно это вместо
    begin
      if FileExists('/sys/class/tty/'+DeviceName+'/type') then PT:=1           // тип порта 1 = фиксированный последовательный порт
                                                          else PT:=2;          // тип порта 2 = съемный/USB
      if PT=1 then
      begin
        FD:=fpOpen('/dev/'+DeviceName, O_RDWR or O_NONBLOCK or O_NOCTTY);
        if FD<0 then PT:=0 else                                                // -1, не удалось открыть дескриптор -> сбой
        begin
          if fpIOCtl(FD,TCGETS,@tios)<>0 then PT:=0                            // TCGETS: при успехе возвращается 0,
                                         else inc(n);                          //         при ошибке возвращается -1
          fpClose(FD)
        end
      end;

      if PT<>0 then inc(c);                                                    // подсчет общего числа портов
      case PT of 1:writeln('* ', DeviceName);                                  // фиксированный последовательный порт, проверенный как реальный
                 2:writeln('  ', DeviceName)                                   // съемный (USB) последовательный порт
      end  {of case}
    end
  until FindNext(SR) <> 0;

  if n<>0 then writeln('(', n, ' фиксированных устройств)');
  if c=0 then writeln('последовательные порты не найдены');
  FindClose(SR)
end.

Проверка на то, что драйвер устройства называется “serial8250”, теперь заменена проверкой на существование псевдографического файла “type”, а результат вызова ioctl/TIOCGSERIAL заменен на успешное выполнение ioctl/TCGETS (возвращаемые данные не используются). Также добавлен вызов fpAccess, чтобы гарантировать, что результаты не возвращаются, если пользователь не является членом группы dialout.

Если вы переведете это на python, пожалуйста, опубликуйте полученный код. Дискуссия, где этот код был разработан, доступна здесь:
https://forum.lazarus.freepascal.org/index.php/topic,69437.msg541387.html#msg541387

А оригинальный код на C++ от Сёрен Холма можно найти примерно на 1/3 пути вниз этой страницы:
https://stackoverflow.com/a/12301542/7879071

Ответ или решение

К сожалению, с переходом на Ubuntu 24.04 многие пользователи сталкиваются с проблемами, связанными с работой с последовательными портами при использовании библиотеки PySerial. Эта ситуация стала особенно актуальной после обновления версий ядра и системы, когда PySerial начала отображать несуществующие последовательные порты, такие как /dev/ttyS0 до /dev/ttyS31, которые на самом деле отсутствуют в аппаратной конфигурации системы.

Проблема с определением последовательных портов

При выполнении команды для определения доступных последовательных портов:

from serial.tools.list_ports import comports
[p.name for p in comports()]

На Ubuntu 24.04 выводится список, содержащий как реальные, так и несуществующие портовые устройства:

['ttyS0', 'ttyS1', ..., 'ttyS30', 'ttyS31', 'ttyUSB0', ..., 'ttyUSB7']

В то время как на Ubuntu 22.04 отобразились только существующие USB-компоненты.

Причины появления несуществующих портов

Как вы заметили, основная проблема заключалась в том, что в Ubuntu 24.04 (и с новыми версиями ядра) система начала сообщать о различном подтипе устройств для последовательных портов. В частности, система теперь показывает подтип serial-base для всех портов, включая несуществующие, в то время как на Ubuntu 22.04 для несуществующих портов использовался подтип platform.

Сравнение подтипов устройств

Тип порта Подтип в Ubuntu 22.04 Подтип в Ubuntu 24.04
Не-сущ. порты (/dev/ttyS*) platform serial-base
Сущ. порты (/dev/ttyS*) pnp serial-base

Эта перемена повлияла на способ, которым PySerial фильтрует порты. Функция comports() фильтрует порты, исключая устройства с подтипом platform, тогда как новые порты имеют подтип serial-base, что приводит к их включению в список несуществующих портов.

Возможные решения

  1. Фильтрация через ваш собственный код:
    Вы можете модифицировать свой скрипт для исключения портов с подтипом serial-base, сохранив только те, которые действительно существуют. Пример обработки последовательных портов, которая исключает несуществующие порты:

    import os
    from serial.tools.list_ports import comports, SysFS
    
    def filter_serial_ports():
        valid_ports = []
        for port in comports():
            # Проверка на существование порта
            if "serial-base" not in port.subsystem and os.path.exists(port.device):
                valid_ports.append(port.device)
        return valid_ports
    
    print(filter_serial_ports())
  2. Проверка наличия драйверов:
    Проверяйте наличие драйверов для последовательных портов, используя вызовы ioctl и проверку существующих файлов в /sys/class/tty/.

Заключение

Смена подтипа для последовательных портов после обновления ядра и системы создает дополнительные сложности для разработчиков, использующих PySerial. Контроль над данным процессом и создание фильтров для определения достоверности последовательных портов позволит избежать дальнейших проблем и обеспечит правильную работу ваших приложений. Если у вас есть вопросы или вы хотите получить дополнительную информацию по данной теме, не стесняйтесь обращаться за поддержкой.

Оцените материал
Добавить комментарий

Капча загружается...