Wie finde ich alle seriellen Geräte (ttyS, ttyUSB, ..) unter Linux, ohne sie zu öffnen?

112

Was ist der richtige Weg, um eine Liste aller verfügbaren seriellen Ports / Geräte auf einem Linux-System zu erhalten?

Mit anderen Worten, wenn ich über alle Geräte in iteriere /dev/ , wie kann ich dann feststellen, welche auf klassische Weise serielle Ports sind, dh solche, die normalerweise Baudraten und RTS / CTS- Flusskontrolle unterstützen?

Die Lösung würde in C codiert.

Ich frage, weil ich eine Bibliothek eines Drittanbieters verwende, die dies eindeutig falsch macht: Sie scheint nur zu iterieren /dev/ttyS*. Das Problem ist, dass es beispielsweise serielle Anschlüsse über USB gibt (bereitgestellt von USB-RS232-Adaptern), die unter / dev / ttyUSB * aufgeführt sind. Wenn ich das Serial-HOWTO auf Linux.org lese , komme ich auf die Idee, dass es mit der Zeit auch andere Namensräume geben wird.

Ich muss also den offiziellen Weg finden, um serielle Geräte zu erkennen. Das Problem ist, dass keine dokumentiert zu sein scheint oder ich sie nicht finden kann.

Ich kann mir vorstellen, dass eine Möglichkeit darin besteht, alle Dateien zu öffnen /dev/tty*und eine bestimmte Datei aufzurufen ioctl(), die nur auf seriellen Geräten verfügbar ist. Wäre das aber eine gute Lösung?

Aktualisieren

hrickards schlug vor, in der Quelle nach "setserial" zu suchen. Sein Code macht genau das, was ich mir vorgestellt habe:

Zunächst wird ein Gerät geöffnet mit:

fd = open (path, O_RDWR | O_NONBLOCK)

Dann ruft es auf:

ioctl (fd, TIOCGSERIAL, &serinfo)

Wenn dieser Aufruf keinen Fehler zurückgibt, handelt es sich anscheinend um ein serielles Gerät.

Ich habe ähnlichen Code in Serial Programming / termios gefunden , der vorschlug, auch die O_NOCTTYOption hinzuzufügen .

Bei diesem Ansatz gibt es jedoch ein Problem:

Als ich diesen Code unter BSD Unix (dh Mac OS X) getestet habe, hat er auch funktioniert. jedoch seriellen Geräten, die über Bluetooth bereitgestellt werden, versucht das System (der Treiber) jedoch, eine Verbindung zum Bluetooth-Gerät herzustellen. Es dauert eine Weile, bis ein Timeout-Fehler auftritt. Dies wird durch einfaches Öffnen des Geräts verursacht. Und ich kann mir vorstellen, dass ähnliche Dinge auch unter Linux passieren können - im Idealfall sollte ich das Gerät nicht öffnen müssen, um seinen Typ herauszufinden. Ich frage mich, ob es auch eine Möglichkeit gibt, ioctlFunktionen ohne Öffnen aufzurufen oder ein Gerät so zu öffnen, dass keine Verbindungen hergestellt werden.

Was soll ich machen?

Thomas Tempelmann
quelle
1
Jemand anonym hatte diese Änderung vorgeschlagen, die abgelehnt wurde, daher lasse ich sie stattdessen hier als Kommentar: Wenn Sie im ioctl-Aufruf das TIOCGSERIAL-Flag anstelle von TIOCMGET verwenden, gibt der Aufruf keinen Fehler mit einigen falschen Pfaden zurück, die dies nicht tun Beziehen Sie sich auf eine COM-Schnittstelle (seriell). Mit dem TIOCMGET-Flag funktioniert ioctl nur mit den COM-Ports, die für den Zugriff auf mögliche TTY- und TTYUSB-Pfade verfügbar sind.
Thomas Tempelmann

Antworten:

78

Das /sysDateisystem sollte viele Informationen für Ihre Suche enthalten. Mein System (2.6.32-40-generic # 87-Ubuntu) schlägt vor:

/sys/class/tty

Hier finden Sie Beschreibungen aller dem System bekannten TTY-Geräte. Ein gekürztes Beispiel:

# 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/

Folgen Sie einem dieser Links:

# 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

Hier deventhält die Datei diese Informationen:

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

Dies ist der Haupt- / Nebenknoten. Diese können im /devVerzeichnis nach benutzerfreundlichen Namen durchsucht werden :

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

Das /sys/class/ttyVerzeichnis enthält alle TTY-Geräte, aber Sie möchten möglicherweise diese lästigen virtuellen Terminals und Pseudo-Terminals ausschließen. Ich schlage vor, Sie untersuchen nur diejenigen, die einen device/driverEintrag haben:

# 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/
AH
quelle
@entalpi Du wirst finden /dev/zero. Denken Sie wirklich, dass dies ein serielles Gerät ist?
AH
Die Suche in / dev ist nutzlos, da Sie bereits den Namen in / sys / class / tty haben (standardmäßig erstellt udev den Knoten / dev / DEVNAME). Was Sie interessiert, ist jeder "symbolische" Link in / dev, der auf ein solches Gerät verweist. Das ist viel schwieriger zu finden.
Xryl669
28

In neueren Kerneln (seit wann nicht sicher) können Sie den Inhalt von / dev / serial auflisten, um eine Liste der seriellen Ports auf Ihrem System zu erhalten. Es handelt sich tatsächlich um Symlinks, die auf den richtigen / dev / node verweisen:

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

Dies ist ein USB-Serial-Adapter, wie Sie sehen können. Beachten Sie, dass das Verzeichnis / dev / serial / nicht vorhanden ist, wenn keine seriellen Schnittstellen im System vorhanden sind. Hoffe das hilft :).

flu0
quelle
3
Dies ist eine Funktion von udev (insbesondere die Konfiguration in /lib/udev/rules.d/??-persistent-serial.rules), die in 2.5 eingeführt wurde.
Ergosys
4
Toller Tipp! Leider glaube ich nicht, dass dies eingebaute serielle Anschlüsse zeigt, nur serielle USB-Anschlüsse (von udev gesehen, wenn angeschlossen). Ich sehe nichts für / dev / serial in Ubuntu 14 in einer VMware-VM (mit ttyS0 / COM1, das von der VM bereitgestellt wird), und die udev-Regeln (60-persistent-serial.rules) beziehen sich nur auf udev-Geräte - Ich glaube nicht, dass udev von den "eingebauten" seriellen ttyS * -Anschlüssen erfährt, sie müssen wie in den anderen Antworten mit ioctl oder ähnlichem getestet werden.
Reed Hedges
ls / dev / serial / ls: Zugriff auf '/ dev / serial /' nicht möglich: Keine solche Datei oder kein solches Verzeichnis Slackware 14.2 aktuell x64
jpka
2
@jpka: Das passiert, wenn kein serielles Gerät zu finden ist. Ich habe wie oben gemacht und es hat funktioniert. Ich habe dann mein (FTDI) serielles Gerät vom USB getrennt und danach den von Ihnen beschriebenen Fehler erzeugt.
Warpspace
13

Ich mache so etwas wie den folgenden Code. Es funktioniert für USB-Geräte und auch für die dummen serial8250-Geräte, von denen wir alle 30 haben - aber nur ein paar davon funktionieren wirklich.

Grundsätzlich verwende ich das Konzept aus früheren Antworten. Zählen Sie zuerst alle tty-Geräte in / sys / class / tty / auf. Geräte, die kein / device-Unterverzeichnis enthalten, werden weggefiltert. / sys / class / tty / console ist ein solches Gerät. Dann werden die Geräte, die tatsächlich Geräte enthalten, als gültige serielle Schnittstelle akzeptiert, abhängig vom Ziel des Treiber-Symlink-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

und für 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

Alle von serial8250 angetriebenen Treiber müssen Sonden sein, die das zuvor erwähnte ioctl verwenden.

        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

Nur der Port, der einen gültigen Gerätetyp meldet, ist gültig.

Die vollständige Quelle für die Aufzählung der seriellen Ports sieht folgendermaßen aus. Ergänzungen sind willkommen.

#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;   
}
Søren Holm
quelle
Lone Link wird als schlechte Antwort angesehen, da es für sich genommen bedeutungslos ist und nicht garantiert wird, dass die Zielressource in Zukunft lebendig bleibt. Bitte versuchen Sie, mindestens eine Zusammenfassung der Informationen beizufügen, auf die Sie verlinken.
j0k
Vielen Dank an Soren dafür, auch wir kennen die APIs und einige Ideen dazu, aber Sie haben wirklich gute Soren gemacht, nochmals vielen Dank.
Ind79ra
12

Ich glaube, ich habe die Antwort in meiner Kernel-Quelldokumentation gefunden: /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 

Hier ist ein Link zu dieser Datei: http://git.kernel.org/?p=linux/kernel/git/next/linux-next.git;a=blob_plain;f=Documentation/filesystems/proc.txt;hb = e8883f8057c0f7c9950fa9f20568f37bfa62f34a

mk2
quelle
Ja, das scheint zu funktionieren. Für diese Lösung muss ich jedoch eine Textdatei lesen und analysieren. Ich frage mich, ob es eine bessere Möglichkeit gibt, dh eine API, mit der ich diese Inhalte in einem strukturierten Binärformat abrufen kann.
Thomas Tempelmann
9

ich fand

dmesg | grep tty

den Job machen.

RealHuman75
quelle
3

Setserial mit der Option -g scheint zu tun, was Sie wollen, und die C-Quelle ist unter http://www.koders.com/c/fid39344DABD14604E70DF1B8FEA7D920A94AF78BF8.aspx verfügbar .

hrickards
quelle
Ich habe mir den Code angesehen und er hat den Fehler, den ich in meiner Frage am Ende erkläre, da er das Gerät öffnen muss, was bereits zu einem Verbindungsversuch führen kann - was wiederum nicht gut ist. Aber vielleicht sind Linux-Treiber in Bezug auf die Bluetooth-Unterstützung intelligenter als der aktuelle OSX-Treiber, da sie nicht sofort eine Verbindung herstellen? Wer weiß? Vielleicht sollte ich eine neue Frage stellen, um das konkret zu klären. Wenn sich herausstellt, dass das in Ordnung ist, kann ich Ihre Antwort auch hier akzeptieren. Hmmm ...
Thomas Tempelmann
3

Ich habe hier kein serielles Gerät, um es zu testen, aber wenn Sie Python und dbus haben, können Sie es selbst versuchen.

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")

Wenn es fehlschlägt, können Sie nach innen suchen hwmanager_i.GetAllDevicesWithProperties() , ob der soeben vermutete Funktionsname "serial" einen anderen Namen hat.

HTH

baol
quelle
2

Ich habe kein serielles USB-Gerät, aber es muss eine Möglichkeit geben, die realen Ports mithilfe der HAL-Bibliotheken direkt zu finden:

====================================================================
#! /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

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

Der veröffentlichte Python-dbus-Code und dieses sh-Skript listen die Bluetooth / dev / rfcomm * -Geräte auf, daher ist dies nicht die beste Lösung.

Beachten Sie, dass auf anderen Unix-Plattformen die seriellen Ports nicht ttyS? Bei einigen seriellen Karten können Sie die Geräte sogar unter Linux benennen. Die Annahme eines Musters in den Namen der seriellen Geräte ist falsch.

kelk1
quelle
Schade, dass HAL aus Ubuntu entfernt wurde (nach 12.04), es hatte einige nette, einfach zu bedienende Tools. Weiß jemand, ob es einen Ersatz für die oben genannten gibt? Aber wenn Sie auf einer Version / Distribution mit HAL sind, sieht das gut aus.
Reed Hedges
2

Die Verwendung von / proc / tty / drivers gibt nur an, welche tty-Treiber geladen sind. Wenn Sie nach einer Liste der seriellen Ports suchen, überprüfen Sie / dev / serial. Sie enthält zwei Unterverzeichnisse: by-id und 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

Vielen Dank an diesen Beitrag: /superuser/131044/how-do-i-know-which-dev-ttys-is-my-serial-port

Blarf
quelle
Anscheinend ist dies distro-abhängig. Ich kann / dev / serial auf meiner Box nicht finden (mit Debian)
SimonC
0

Mein Ansatz über Gruppen- Dialout , um jedes tty mit Benutzer 'Dialout' ls -l /dev/tty* | grep 'dialout' zu bekommen , um nur seinen Ordner zu bekommen ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev

Hören Sie sich einfach die tty-Ausgabe an, z. B. wenn Arduino seriell ausgeht: head --lines 1 < /dev/ttyUSB0

Hören Sie sich jede Zeile nur für eine Zeile an: for i in $(ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev); do head --lines 1 < $i; done

Ich mag den Ansatz über die Suche nach Fahrern sehr: ll /sys/class/tty/*/device/driver

Sie können jetzt den tty-Namen auswählen: ls /sys/class/tty/*/device/driver | grep 'driver' | cut -d "/" -f 5

McPeppr
quelle
0

Die Serial Communication Manager-Bibliothek verfügt über viele APIs und Funktionen, die auf die gewünschte Aufgabe zugeschnitten sind. Wenn das Gerät ein USB-UART ist, kann seine VID / PID verwendet werden. Wenn das Gerät BT-SPP ist, können plattformspezifische APIs verwendet werden. Schauen Sie sich dieses Projekt für die Programmierung der seriellen Schnittstelle an: https://github.com/RishiGupta12/serial-communication-manager

samuel05051980
quelle
0

Ja, ich weiß, ich bin zu spät (wie immer). Hier ist mein Code (basierend auf der Antwort von mk2). Vielleicht hilft das jemandem:

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;
}
cdannebe
quelle
Es scheint, dass Ihr Code analysiert, worauf sich die Antwort stackoverflow.com/a/4701610/43615 bezieht. Wenn ja, würden Sie das bitte in Ihrer Antwort erwähnen?
Thomas Tempelmann