Comment trouver tous les périphériques série (ttyS, ttyUSB, ..) sous Linux sans les ouvrir?


113

Quelle est la bonne façon d'obtenir une liste de tous les ports / périphériques série disponibles sur un système Linux?

En d'autres termes, lorsque j'effectue une itération sur tous les périphériques /dev/, comment puis-je savoir quels sont les ports série de manière classique, c'est-à-dire ceux qui prennent généralement en charge les débits en bauds et le contrôle de flux RTS / CTS ?

La solution serait codée en C.

Je demande parce que j'utilise une bibliothèque tierce qui fait clairement cela mal: il semble seulement itérer /dev/ttyS*. Le problème est qu'il existe, par exemple, des ports série via USB (fournis par des adaptateurs USB-RS232), et ceux-ci sont répertoriés sous / dev / ttyUSB *. Et lire le Serial-HOWTO sur Linux.org , j'ai l'idée qu'il y aura aussi d'autres espaces de noms, avec le temps.

J'ai donc besoin de trouver le moyen officiel de détecter les périphériques série. Le problème est qu'aucun ne semble être documenté, ou je ne le trouve pas.

J'imagine qu'une façon serait d'ouvrir tous les fichiers /dev/tty*et d'en appeler un spécifique ioctl()qui n'est disponible que sur les périphériques série. Serait-ce une bonne solution, cependant?

Mettre à jour

hrickards suggéré de regarder la source de "setserial". Son code fait exactement ce que j'avais en tête:

Tout d'abord, il ouvre un appareil avec:

fd = open (path, O_RDWR | O_NONBLOCK)

Ensuite, il invoque:

ioctl (fd, TIOCGSERIAL, &serinfo)

Si cet appel ne renvoie aucune erreur, alors c'est un périphérique série, apparemment.

J'ai trouvé un code similaire dans la programmation série / termios , ce qui suggérait d'ajouter également l' O_NOCTTYoption.

Il y a cependant un problème avec cette approche:

Lorsque j'ai testé ce code sur BSD Unix (c'est-à-dire Mac OS X), cela a également fonctionné. Cependant , les périphériques série fournis via Bluetooth obligent le système (pilote) à essayer de se connecter au périphérique Bluetooth, ce qui prend un certain temps avant qu'il ne revienne avec une erreur de temporisation. Cela est dû à la simple ouverture de l'appareil. Et je peux imaginer que des choses similaires peuvent également se produire sous Linux - idéalement, je ne devrais pas avoir besoin d'ouvrir l'appareil pour déterminer son type. Je me demande s'il existe également un moyen d'invoquer des ioctlfonctions sans ouvrir ou d'ouvrir un périphérique de manière à ne pas créer de connexions?

Que devrais-je faire?


1
Quelqu'un anonyme avait suggéré cette modification, qui a été rejetée, donc je la laisse ici comme commentaire à la place: Si vous utilisez l'indicateur TIOCGSERIAL dans l'appel ioctl, au lieu de TIOCMGET, l'appel ne renvoie pas d'erreur avec des chemins erronés qui ne le font pas se référer à un port COM (série). Avec l'indicateur TIOCMGET, ioctl fonctionne uniquement avec les ports COM disponibles pour accéder dans les chemins possibles TTY et TTYUSB.
Thomas Tempelmann

Réponses:


78

Le /syssystème de fichiers devrait contenir de nombreuses informations pour votre quête. Mon système (2.6.32-40-generic # 87-Ubuntu) suggère:

/sys/class/tty

Ce qui vous donne une description de tous les appareils TTY connus du système. Un exemple réduit:

# ll /sys/class/tty/ttyUSB*
lrwxrwxrwx 1 root root 0 2012-03-28 20:43 /sys/class/tty/ttyUSB0 -> ../../devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4/2-1.4:1.0/ttyUSB0/tty/ttyUSB0/
lrwxrwxrwx 1 root root 0 2012-03-28 20:44 /sys/class/tty/ttyUSB1 -> ../../devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.3/2-1.3:1.0/ttyUSB1/tty/ttyUSB1/

En suivant l'un de ces liens:

# ll /sys/class/tty/ttyUSB0/
insgesamt 0
drwxr-xr-x 3 root root    0 2012-03-28 20:43 ./
drwxr-xr-x 3 root root    0 2012-03-28 20:43 ../
-r--r--r-- 1 root root 4096 2012-03-28 20:49 dev
lrwxrwxrwx 1 root root    0 2012-03-28 20:43 device -> ../../../ttyUSB0/
drwxr-xr-x 2 root root    0 2012-03-28 20:49 power/
lrwxrwxrwx 1 root root    0 2012-03-28 20:43 subsystem -> ../../../../../../../../../../class/tty/
-rw-r--r-- 1 root root 4096 2012-03-28 20:43 uevent

Ici, le devfichier contient ces informations:

# cat /sys/class/tty/ttyUSB0/dev
188:0

C'est le nœud majeur / mineur. Ceux-ci peuvent être recherchés dans le /devrépertoire pour obtenir des noms conviviaux:

# ll -R /dev |grep "188, *0"
crw-rw----   1 root dialout 188,   0 2012-03-28 20:44 ttyUSB0

Le /sys/class/ttyrépertoire contient tous les périphériques TTY, mais vous voudrez peut-être exclure ces terminaux virtuels et pseudo terminaux. Je vous suggère d'examiner uniquement ceux qui ont une device/driverentrée:

# ll /sys/class/tty/*/device/driver
lrwxrwxrwx 1 root root 0 2012-03-28 19:07 /sys/class/tty/ttyS0/device/driver -> ../../../bus/pnp/drivers/serial/
lrwxrwxrwx 1 root root 0 2012-03-28 19:07 /sys/class/tty/ttyS1/device/driver -> ../../../bus/pnp/drivers/serial/
lrwxrwxrwx 1 root root 0 2012-03-28 19:07 /sys/class/tty/ttyS2/device/driver -> ../../../bus/platform/drivers/serial8250/
lrwxrwxrwx 1 root root 0 2012-03-28 19:07 /sys/class/tty/ttyS3/device/driver -> ../../../bus/platform/drivers/serial8250/
lrwxrwxrwx 1 root root 0 2012-03-28 20:43 /sys/class/tty/ttyUSB0/device/driver -> ../../../../../../../../bus/usb-serial/drivers/ftdi_sio/
lrwxrwxrwx 1 root root 0 2012-03-28 21:15 /sys/class/tty/ttyUSB1/device/driver -> ../../../../../../../../bus/usb-serial/drivers/ftdi_sio/

@entalpi Vous trouverez /dev/zero. Pensez-vous vraiment qu'il s'agit d'un périphérique série?
AH

La recherche dans / dev est inutile, puisque vous avez déjà le nom dans / sys / class / tty (comme par défaut udev crée le nœud / dev / DEVNAME). Ce qui vous intéresse, c'est tout lien «symbolique» dans / dev qui pointe vers un tel appareil. C'est beaucoup plus difficile à trouver.
xryl669

28

Dans les noyaux récents (je ne sais pas depuis quand), vous pouvez lister le contenu de / dev / serial pour obtenir une liste des ports série de votre système. Ce sont en fait des liens symboliques pointant vers le bon nœud / dev /:

flu0@laptop:~$ ls /dev/serial/
total 0
drwxr-xr-x 2 root root 60 2011-07-20 17:12 by-id/
drwxr-xr-x 2 root root 60 2011-07-20 17:12 by-path/
flu0@laptop:~$ ls /dev/serial/by-id/
total 0
lrwxrwxrwx 1 root root 13 2011-07-20 17:12 usb-Prolific_Technology_Inc._USB-Serial_Controller-if00-port0 -> ../../ttyUSB0
flu0@laptop:~$ ls /dev/serial/by-path/
total 0
lrwxrwxrwx 1 root root 13 2011-07-20 17:12 pci-0000:00:0b.0-usb-0:3:1.0-port0 -> ../../ttyUSB0

Il s'agit d'un adaptateur USB-série, comme vous pouvez le voir. Notez que lorsqu'il n'y a pas de port série sur le système, le répertoire / dev / serial / n'existe pas. J'espère que cela t'aides :).


3
C'est une fonction d'udev (en particulier sa configuration dans /lib/udev/rules.d/??-persistent-serial.rules), qui a été introduite dans la version 2.5.
ergosys

4
Bon conseil! Malheureusement, je ne pense pas que cela affichera les ports série intégrés, uniquement les ports série USB (vus par udev lorsqu'ils sont connectés). Je ne vois rien pour / dev / serial dans Ubuntu 14 dans une VM VMware (avec ttyS0 / COM1 fourni par la VM), et les règles udev (60-persistent-serial.rules) ne concernent que les périphériques udev - Je ne pense pas que udev découvre les ports série ttyS * "intégrés", ils devront être testés avec ioctl ou similaire comme dans les autres réponses.
Reed Hedges

ls / dev / serial / ls: impossible d'accéder à '/ dev / serial /': aucun fichier ou répertoire de ce type Slackware 14.2 current x64
jpka

2
@jpka: Cela se produit s'il n'y a pas de périphérique série à trouver. J'ai fait comme ci-dessus et cela a fonctionné. J'ai ensuite débranché mon périphérique série (FTDI) de l'USB et ensuite cela a produit l'erreur que vous avez décrite.
Warpspace

13

Je fais quelque chose comme le code suivant. Cela fonctionne pour les périphériques USB et aussi les stupides périphériques serial8250 dont nous avons tous 30 - mais seuls quelques-uns d'entre eux fonctionnent vraiment.

Fondamentalement, j'utilise le concept des réponses précédentes. Commencez par énumérer tous les périphériques tty dans / sys / class / tty /. Les périphériques qui ne contiennent pas de sous-répertoire / device sont filtrés. / sys / class / tty / console est un tel périphérique. Ensuite, les périphériques contenant réellement un périphérique sont alors acceptés comme port série valide en fonction de la cible du pilote-lien symbolique fx.

$ ls -al /sys/class/tty/ttyUSB0//device/driver
lrwxrwxrwx 1 root root 0 sep  6 21:28 /sys/class/tty/ttyUSB0//device/driver -> ../../../bus/platform/drivers/usbserial

et pour ttyS0

$ ls -al /sys/class/tty/ttyS0//device/driver
lrwxrwxrwx 1 root root 0 sep  6 21:28 /sys/class/tty/ttyS0//device/driver -> ../../../bus/platform/drivers/serial8250

Tous les pilotes pilotés par serial8250 doivent être des sondes utilisant l'ioctl mentionné précédemment.

        if (ioctl(fd, TIOCGSERIAL, &serinfo)==0) {
            // If device type is no PORT_UNKNOWN we accept the port
            if (serinfo.type != PORT_UNKNOWN)
                the_port_is_valid

Seul le port signalant un type de périphérique valide est valide.

La source complète d'énumération des ports série ressemble à ceci. Les ajouts sont les bienvenus.

#include <stdlib.h>
#include <dirent.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <linux/serial.h>

#include <iostream>
#include <list>

using namespace std;

static string get_driver(const string& tty) {
    struct stat st;
    string devicedir = tty;

    // Append '/device' to the tty-path
    devicedir += "/device";

    // Stat the devicedir and handle it if it is a symlink
    if (lstat(devicedir.c_str(), &st)==0 && S_ISLNK(st.st_mode)) {
        char buffer[1024];
        memset(buffer, 0, sizeof(buffer));

        // Append '/driver' and return basename of the target
        devicedir += "/driver";

        if (readlink(devicedir.c_str(), buffer, sizeof(buffer)) > 0)
            return basename(buffer);
    }
    return "";
}

static void register_comport( list<string>& comList, list<string>& comList8250, const string& dir) {
    // Get the driver the device is using
    string driver = get_driver(dir);

    // Skip devices without a driver
    if (driver.size() > 0) {
        string devfile = string("/dev/") + basename(dir.c_str());

        // Put serial8250-devices in a seperate list
        if (driver == "serial8250") {
            comList8250.push_back(devfile);
        } else
            comList.push_back(devfile); 
    }
}

static void probe_serial8250_comports(list<string>& comList, list<string> comList8250) {
    struct serial_struct serinfo;
    list<string>::iterator it = comList8250.begin();

    // Iterate over all serial8250-devices
    while (it != comList8250.end()) {

        // Try to open the device
        int fd = open((*it).c_str(), O_RDWR | O_NONBLOCK | O_NOCTTY);

        if (fd >= 0) {
            // Get serial_info
            if (ioctl(fd, TIOCGSERIAL, &serinfo)==0) {
                // If device type is no PORT_UNKNOWN we accept the port
                if (serinfo.type != PORT_UNKNOWN)
                    comList.push_back(*it);
            }
            close(fd);
        }
        it ++;
    }
}

list<string> getComList() {
    int n;
    struct dirent **namelist;
    list<string> comList;
    list<string> comList8250;
    const char* sysdir = "/sys/class/tty/";

    // Scan through /sys/class/tty - it contains all tty-devices in the system
    n = scandir(sysdir, &namelist, NULL, NULL);
    if (n < 0)
        perror("scandir");
    else {
        while (n--) {
            if (strcmp(namelist[n]->d_name,"..") && strcmp(namelist[n]->d_name,".")) {

                // Construct full absolute file path
                string devicedir = sysdir;
                devicedir += namelist[n]->d_name;

                // Register the device
                register_comport(comList, comList8250, devicedir);
            }
            free(namelist[n]);
        }
        free(namelist);
    }

    // Only non-serial8250 has been added to comList without any further testing
    // serial8250-devices must be probe to check for validity
    probe_serial8250_comports(comList, comList8250);

    // Return the lsit of detected comports
    return comList;
}


int main() {
    list<string> l = getComList();

    list<string>::iterator it = l.begin();
    while (it != l.end()) {
        cout << *it << endl;
        it++;
    }

    return 0;   
}

Un lien isolé est considéré comme une mauvaise réponse car il n'a pas de sens en soi et la ressource cible n'est pas garantie d'être vivante dans le futur. Veuillez essayer d'inclure au moins un résumé des informations auxquelles vous créez un lien.
j0k

Merci à Soren pour cela, même nous connaissons les API et quelques idées dessus, mais vous avez vraiment bien fait Soren, merci encore.
ind79ra

12

Je pense avoir trouvé la réponse dans ma documentation source du noyau: /usr/src/linux-2.6.37-rc3/Documentation/filesystems/proc.txt

1.7 TTY info in /proc/tty
-------------------------

Information about  the  available  and actually used tty's can be found in the
directory /proc/tty.You'll  find  entries  for drivers and line disciplines in
this directory, as shown in Table 1-11.


Table 1-11: Files in /proc/tty
..............................................................................
 File          Content                                        
 drivers       list of drivers and their usage                
 ldiscs        registered line disciplines                    
 driver/serial usage statistic and status of single tty lines 
..............................................................................

To see  which  tty's  are  currently in use, you can simply look into the file
/proc/tty/drivers:

  > cat /proc/tty/drivers 
  pty_slave            /dev/pts      136   0-255 pty:slave 
  pty_master           /dev/ptm      128   0-255 pty:master 
  pty_slave            /dev/ttyp       3   0-255 pty:slave 
  pty_master           /dev/pty        2   0-255 pty:master 
  serial               /dev/cua        5   64-67 serial:callout 
  serial               /dev/ttyS       4   64-67 serial 
  /dev/tty0            /dev/tty0       4       0 system:vtmaster 
  /dev/ptmx            /dev/ptmx       5       2 system 
  /dev/console         /dev/console    5       1 system:console 
  /dev/tty             /dev/tty        5       0 system:/dev/tty 
  unknown              /dev/tty        4    1-63 console 

Voici un lien vers ce fichier: http://git.kernel.org/?p=linux/kernel/git/next/linux-next.git;a=blob_plain;f=Documentation/filesystems/proc.txt;hb = e8883f8057c0f7c9950fa9f20568f37bfa62f34a


Oui, cela semble fonctionner. Cependant, cette solution m'oblige à lire un fichier texte et à l'analyser. Je me demande s'il existe une meilleure solution, à savoir une API qui me permet d'obtenir ces contenus dans un format binaire structuré.
Thomas Tempelmann


3

setserial avec l'option -g semble faire ce que vous voulez et la source C est disponible à http://www.koders.com/c/fid39344DABD14604E70DF1B8FEA7D920A94AF78BF8.aspx .


J'ai regardé le code et il a le défaut que j'explique dans ma question à la fin car il doit ouvrir l'appareil, ce qui peut déjà conduire à une tentative de connexion - ce qui à son tour n'est pas bon. Mais alors, peut-être que les pilotes Linux sont plus intelligents que les pilotes OSX actuels en ce qui concerne le support Bluetooth, car ils n'ouvriront pas de connexion tout de suite? Qui sait? Je devrais peut-être commencer une nouvelle question pour clarifier cela précisément. S'il s'avère que cela vous convient, je peux également accepter votre réponse ici. Hmmm ...
Thomas Tempelmann

3

Je n'ai pas de périphérique série ici pour le tester, mais si vous avez python et dbus, vous pouvez l'essayer vous-même.

import dbus
bus = dbus.SystemBus()
hwmanager = bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager')
hwmanager_i = dbus.Interface(hwmanager, 'org.freedesktop.Hal.Manager')
print hwmanager_i.FindDeviceByCapability("serial")

En cas d'échec, vous pouvez rechercher à l'intérieur hwmanager_i.GetAllDevicesWithProperties()pour voir si le nom de capacité «série» que je viens de deviner a un nom différent.

HTH


2

Je n'ai pas de périphérique série USB, mais il doit y avoir un moyen de trouver les ports réels en utilisant directement les bibliothèques HAL:

====================================================================
#! /usr/bin/env bash
#
# Uses HAL to find existing serial hardware
#

for sport in $(hal-find-by-capability --capability serial) ; do
  hal-get-property --udi "${sport}" --key serial.device
done

====================================================================

Le code python-dbus publié ni ce script sh ne répertorie les périphériques bluetooth / dev / rfcomm *, ce n'est donc pas la meilleure solution.

Notez que sur les autres plates-formes unix, les ports série ne sont pas nommés ttyS? et même sous Linux, certaines cartes série vous permettent de nommer les appareils. En supposant qu'un modèle dans les noms des périphériques série est incorrect.


Dommage que HAL ait été supprimé d'Ubuntu (après 12.04), il disposait de bons outils faciles à utiliser. Est-ce que quelqu'un sait s'il y a un remplacement à ce qui précède? Mais si vous êtes sur une version / distribution qui a HAL, cela a l'air bien.
Reed Hedges

2

L'utilisation de / proc / tty / drivers indique uniquement quels pilotes tty sont chargés. Si vous recherchez une liste des ports série check out / dev / serial, il aura deux sous-répertoires: by-id et by-path.

EX:

# find . -type l
./by-path/usb-0:1.1:1.0-port0
./by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller-if00-port0

Merci à ce post: /superuser/131044/how-do-i-know-which-dev-ttys-is-my-serial-port


Apparemment, cela dépend de la distribution. Je ne trouve pas / dev / serial sur ma boîte (sous Debian)
SimonC

0

Mon approche via la numérotation de groupe pour obtenir chaque tty avec l'utilisateur `` dialout '' ls -l /dev/tty* | grep 'dialout' pour obtenir uniquement son dossier ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev

écoute facile de la sortie tty, par exemple quand une sortie série arduino: head --lines 1 < /dev/ttyUSB0

écoutez chaque tty sur une seule ligne: for i in $(ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev); do head --lines 1 < $i; done

J'aime beaucoup l'approche via la recherche de pilotes: ll /sys/class/tty/*/device/driver

Vous pouvez choisir le tty-Name maintenant: ls /sys/class/tty/*/device/driver | grep 'driver' | cut -d "/" -f 5


0

La bibliothèque du gestionnaire de communication série comprend de nombreuses API et fonctionnalités ciblées pour la tâche souhaitée. Si l'appareil est un USB-UART, son VID / PID peut être utilisé. Si le périphérique est BT-SPP, des API spécifiques à la plate-forme peuvent être utilisées. Jetez un œil à ce projet pour la programmation du port série: https://github.com/RishiGupta12/serial-communication-manager


0

oui, je sais, je suis trop tard (comme toujours). Voici mon morceau de code (basé sur la réponse de mk2). Peut-être que cela aide quelqu'un:

std::vector<std::string> find_serial_ports()
{
 std::vector<std::string> ports;
    std::filesystem::path kdr_path{"/proc/tty/drivers"};
    if (std::filesystem::exists(kdr_path))
    {
        std::ifstream ifile(kdr_path.generic_string());
        std::string line;
        std::vector<std::string> prefixes;
        while (std::getline(ifile, line))
        {
            std::vector<std::string> items;
            auto it = line.find_first_not_of(' ');
            while (it != std::string::npos)
            {

                auto it2 = line.substr(it).find_first_of(' ');
                if (it2 == std::string::npos)
                {
                    items.push_back(line.substr(it));
                    break;
                }
                it2 += it;
                items.push_back(line.substr(it, it2 - it));
                it = it2 + line.substr(it2).find_first_not_of(' ');
            }
            if (items.size() >= 5)
            {
                if (items[4] == "serial" && items[0].find("serial") != std::string::npos)
                {
                    prefixes.emplace_back(items[1]);
                }
            }
        }
        ifile.close();
        for (auto& p: std::filesystem::directory_iterator("/dev"))
        {
            for (const auto& pf : prefixes)
            {
                auto dev_path = p.path().generic_string();
                if (dev_path.size() >= pf.size() && std::equal(dev_path.begin(), dev_path.begin() + pf.size(), pf.begin()))
                {
                    ports.emplace_back(dev_path);
                }
            }
        }
    }
    return ports;
}

Il semble que votre code analyse la réponse à laquelle se réfère stackoverflow.com/a/4701610/43615 . Si oui, pourriez-vous le mentionner dans votre réponse, s'il vous plaît?
Thomas Tempelmann
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.