Версия, подходяща за принтиране
Кликни тук, за да видиш темата в оригиналният и вид
BG Development Форуми > .NET програмиране > Достъп до серийните портове под linux


Публикувано от: thrawn 28-10-2025, 16:13
Цял ден се мъча да получа списък със серийните портове на Linux машина. Проблемът е, че SerialPort.GetPortNames() не ги вижда. Ако се опитам да отворя порт който знам, че е там (например /dev/ttyS0) се отваря успешно.

Това е инсталирано на машината:
CODE

.NET SDK:
Version:           8.0.121
Commit:            5b9595625d
Workload version:  8.0.100-manifests.21648041

Runtime Environment:
OS Name:     ubuntu
OS Version:  24.04
OS Platform: Linux
RID:         ubuntu.24.04-x64
Base Path:   /usr/lib/dotnet/sdk/8.0.121/

.NET workloads installed:
Workload version: 8.0.100-manifests.21648041
There are no installed workloads to display.

Host:
 Version:      8.0.21
 Architecture: x64
 Commit:       362ab6669d

.NET SDKs installed:
 8.0.121 [/usr/lib/dotnet/sdk]

.NET runtimes installed:
 Microsoft.AspNetCore.App 8.0.21 [/usr/lib/dotnet/shared/Microsoft.AspNetCore.App]
 Microsoft.NETCore.App 8.0.21 [/usr/lib/dotnet/shared/Microsoft.NETCore.App]

Other architectures found:
 None

Environment variables:
 Not set

global.json file:
 Not found

Learn more:
 https://aka.ms/dotnet/info

Download .NET:
 https://aka.ms/dotnet/download

Публикувано от: ici 28-10-2025, 16:39
Ами мен лично .NET ме отвращава, но на Питон PySerial си ги вижда, а за C++ boost::asio проверявам /dev/ttyS* и /dev/ttyUSB* и няма проблем.

Публикувано от: thrawn 28-10-2025, 16:50
И мен ме отвращава, ама няма особен избор сега.

Всичко е ОК с портовете. Работят си, имам права и се менаджират коректно от udev (добавям постоянни линкове за usb устройствата). Виждам си ги под java, но .net не ги вижда. Конкретно функцията GetPortNames() връща празен масив.

Но въпреки, че не се вижда портът, ако се опитам да го отворя като изрично посоча името му се отваря без проблеми (под .net).

Сега свалих платформата директно от MS и пак е същата работа
CODE
.NET SDK:
Version:           8.0.415
Commit:            7bd5a8c970
Workload version:  8.0.400-manifests.c414f008
MSBuild version:   17.11.48+02bf66295


Между другото с chatgtp циклим в кръг - той твърди че трябва да имам libSystem.IO.Ports.Native.so която да ползва libudev. На една от платформите с които се мъча я има билиотеката но не ползва libudev
CODE
ldd libSystem.IO.Ports.Native.so
       linux-vdso.so.1 (0x00007ffe643e1000)
       libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007283fbc00000)
       /lib64/ld-linux-x86-64.so.2 (0x00007283fbeda000)


Но и с нея не става.

След това твърди, че от версия 6 в платформата тая библиотека не била отделна и ми казва да сваля официалната исталация защото моята била орязана ...

Публикувано от: ici 28-10-2025, 17:13
С libc.so.6 аз имах големи болки които не можах да реша. Исках да направя портабъл приложение за Linux със PyInstaller, но работеше само на моята машина заради libc.so.6. В крайна сметка го направих като *.pyz, прави venv, качва с pip каквото му трябва и така работи.

Публикувано от: thrawn 28-10-2025, 18:50
Вече го докарахме до тук
CODE
От .NET 6 и нагоре, System.IO.Ports под Linux има следното поведение:

Методът SerialPort.GetPortNames() не връща всички /dev/ttyS* устройства автоматично.

Той обикновено връща само USB базирани tty устройства (ttyUSB*, ttyACM*) и виртуални серийни портове.

Стандартните „builtin“ UART порта (ttyS0, ttyS1 и т.н.) често не се включват в списъка, дори ако udev ги знае.

Тоест това е поведение по дизайн на .NET Core/8 под Linux, а не проблем с библиотеката или права.

Публикувано от: akrachev 28-10-2025, 20:15
ОК - тогава правиш проба да инициализираш порт - ако има грешка - значи няма!

Публикувано от: thrawn 29-10-2025, 06:48
Тествал съм. Знам, че имам /dev/ttyS0 на машината. GetPortNames() връща празен масив. Въпреки това пробвам да отворя порт с това име и да пратя нещо по него. Портът се отваря и данните се пращат.

Днес смятам да свалям версията на платформата под 6 - 5, 4, 3 и 2 за да проверя теорията на бота за библиотеката и да видя дали в някоя версия на платформата тя няма да използва наистина libudev за да ги изброява.

Публикувано от: relax4o 29-10-2025, 15:26
Предполагам си reference-нал правилната версия на System.IO.Ports в проекта. Аз мога да потвърдя под Mac (въпреки, че е Unix, е отделен метод, който чете, така че не е доказателство), с .NET 9 GetPortNames() ми връща устройства.

По-късно ще тествам и на линукс машината, отново с 9 (а може и с 8) да видим какво ще стане и дали ще върне нещо.

Публикувано от: ici 29-10-2025, 15:53
Едва ли имаш истински сериен порт, по-скоро имаш някакво USB CDC устройство с което няма проблеми.

Публикувано от: relax4o 29-10-2025, 16:36
QUOTE (ici @ 29-10-2025, 15:53)
Едва ли имаш истински сериен порт, по-скоро имаш някакво USB CDC устройство с което няма проблеми.

QUOTE

> sudo dmesg | grep ttyS
[    0.444780] serial8250: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A
[    5.145522] tty ttyS15: hash matches



Това вижда dmesg, но SerialPort.GetPortNames() нищо не вижда и при мен под Линукс. Предполагам, че трябваше да ми върне поне ttyS0 в случая.

Иначе има една архивирана библиотека NetCoreSerial, но при нея SerialDevice.GetPortNames() просто връща списък с всички /dev/ttyS0-N. Не съм сигурен дали това би ти помогнало за твоя случай.

Публикувано от: thrawn 29-10-2025, 16:59
Да, ще помогне (ако проблемът е в библиотеката).

Ако това което имаш работи коректно и има натив библиотеката я погледни с ldd дали ползва libudev, както твърди бота че трябва да е.

Публикувано от: relax4o 29-10-2025, 20:20
Изглежда .NET-аджиите никога не са ползвали libudev. Всичко е четене на файлове и директории.

Ето това е, което прави https://github.com/dotnet/runtime/blob/v9.0.10/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialPort.Unix.cs#L32-L91

Като цяло този метод не е мърдал въобще.

Аз също нямам натив библиотеката, даже се учудвам, че на някоя от системите, които си тествал я има.

Иначе с помощта на чатгпт ми сътвори това, което използва libudev API-а. Нямам никакъв опит с такъв тип програмиране на C#, така че адекватно мнение не мога да дам, но изглежда да работи. Само го накарах да филтрира само сериал портове.

CODE

using System.Runtime.InteropServices;

class Program
{
   private const string LibUdev = "libudev.so.1"; // Or "libudev.so" depending on system

   [DllImport(LibUdev)]
   private static extern IntPtr udev_new();

   [DllImport(LibUdev)]
   private static extern IntPtr udev_unref(IntPtr udev);

   [DllImport(LibUdev)]
   private static extern IntPtr udev_enumerate_new(IntPtr udev);

   [DllImport(LibUdev, CharSet = CharSet.Unicode)]
   private static extern int udev_enumerate_add_match_subsystem(IntPtr enumerate, string subsystem);

   [DllImport(LibUdev)]
   private static extern int udev_enumerate_scan_devices(IntPtr enumerate);

   [DllImport(LibUdev)]
   private static extern IntPtr udev_enumerate_get_list_entry(IntPtr enumerate);

   [DllImport(LibUdev)]
   private static extern IntPtr udev_list_entry_get_next(IntPtr listEntry);

   [DllImport(LibUdev)]
   private static extern IntPtr udev_list_entry_get_name(IntPtr listEntry);

   [DllImport(LibUdev, CharSet = CharSet.Unicode)]
   private static extern IntPtr udev_device_new_from_syspath(IntPtr udev, string syspath);

   [DllImport(LibUdev)]
   private static extern IntPtr udev_device_get_devnode(IntPtr device);

   [DllImport(LibUdev)]
   private static extern void udev_device_unref(IntPtr device);

   [DllImport(LibUdev)]
   private static extern void udev_enumerate_unref(IntPtr enumerate);
   
   [DllImport(LibUdev, CharSet = CharSet.Unicode)]
   private static extern IntPtr udev_device_get_parent_with_subsystem_devtype(IntPtr device, string subsystem, string devtype);

   private static void Main()
   {
       var udev = udev_new();
       var enumerate = udev_enumerate_new(udev);
       udev_enumerate_add_match_subsystem(enumerate, "tty");
       udev_enumerate_scan_devices(enumerate);

       var entry = udev_enumerate_get_list_entry(enumerate);
       while (entry != IntPtr.Zero)
       {
           var path = Marshal.PtrToStringAnsi(udev_list_entry_get_name(entry));
           var dev = udev_device_new_from_syspath(udev, path);
           var node = Marshal.PtrToStringAnsi(udev_device_get_devnode(dev));

           // Filter: only real hardware serial ports (USB/ACM/ttyS)
           if (!string.IsNullOrEmpty(node) && (
                   node.StartsWith("/dev/ttyUSB") ||
                   node.StartsWith("/dev/ttyACM") ||
                   node.StartsWith("/dev/ttyS")))
           {
               // Check if it belongs to a USB device
               var parent = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device");
               if (parent != IntPtr.Zero || node.StartsWith("/dev/ttyS"))
               {
                   Console.WriteLine($"Serial device: {node}");
               }
           }

           udev_device_unref(dev);
           entry = udev_list_entry_get_next(entry);
       }

       udev_enumerate_unref(enumerate);
       udev_unref(udev);
   }
}


Очевидно API-а може да се разшири и да използва мониторинг за плъг/ънплъг, но предполагам от тук натам, ако решиш този вариант гугъл и чатгпт биха ти помогнали повече от мен.
Ако този метод и на теб не ти допада, то тогава NetCoreSerial изглежда ще е това, което да ти свърши работа.

Надявам се да съм помогнал.

Публикувано от: thrawn 30-10-2025, 06:46
Гитхъбът ще свърши перфектна работа. Ще го ползвам за да видя точно какво не му харесва в системата и ще го коригирам с udev.

10x

Публикувано от: dvader 30-10-2025, 08:11
Абе тоя .нет не е ли опен сорс?
Какво пречи да се види какво що и как прави?
А и да не е опен-сорс за .нет има декомпилатори, включително май в самия .нет.

Публикувано от: relax4o 30-10-2025, 10:13
QUOTE (dvader @ 30-10-2025, 08:11)
Абе тоя .нет не е ли опен сорс?
Какво пречи да се види какво що и как прави?
А и да не е опен-сорс за .нет има декомпилатори, включително май в самия .нет.

Е да де

https://github.com/dotnet/runtime/blob/v9.0.10/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialPort.Unix.cs#L32-L91

Публикувано от: ici 30-10-2025, 12:15
Виж и на Питон как става: https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports_linux.py

ПП. Казах на Gemini да ми направи cpp код за това от питонският файл, и го компилирах в WSL/Debian, след което закачих на Дебиана една платка с УСБ ЦДЦ със https://learn.microsoft.com/en-us/windows/wsl/connect-usb. С УСБ-тата няма проблем, но вади списък с ttyS0-7 който няма нищо общо с истината.
CODE
debian@pc6136:/mnt/c/Projects/cmake/cesterminal/src/communication$ ./a.out
Available Serial Ports:
------------------------------------------------------------------------
Device:      /dev/ttyS0
Description: Serial Port
Hardware ID: N/A
------------------------------------------------------------------------
Device:      /dev/ttyS1
Description: Serial Port
Hardware ID: N/A
------------------------------------------------------------------------
Device:      /dev/ttyS2
Description: Serial Port
Hardware ID: N/A
------------------------------------------------------------------------
Device:      /dev/ttyS3
Description: Serial Port
Hardware ID: N/A
------------------------------------------------------------------------
Device:      /dev/ttyS4
Description: Serial Port
Hardware ID: N/A
------------------------------------------------------------------------
Device:      /dev/ttyS5
Description: Serial Port
Hardware ID: N/A
------------------------------------------------------------------------
Device:      /dev/ttyS6
Description: Serial Port
Hardware ID: N/A
------------------------------------------------------------------------
Device:      /dev/ttyS7
Description: Serial Port
Hardware ID: N/A
------------------------------------------------------------------------
Device:      /dev/ttyACM0
Description: NNN - NNN_FS_CDC
Hardware ID: USB VID:PID=0nnn:0nnn SER=NNNN
------------------------------------------------------------------------
debian@pc6136:/mnt/c/Projects/cmake/cesterminal/src/communication$

Публикувано от: thrawn 30-10-2025, 16:39
Проблемът е в атрибутите на устройството които драйверът му задава
CODE
/sys/class/tty/ttyS0/device
├── driver -> ../../../../../bus/serial-base/drivers/port
├── power
│   ├── async
│   ├── autosuspend_delay_ms
│   ├── control
│   ├── runtime_active_kids
│   ├── runtime_active_time
│   ├── runtime_enabled
│   ├── runtime_status
│   ├── runtime_suspended_time
│   └── runtime_usage
├── subsystem -> ../../../../../bus/serial-base
├── tty
│   └── ttyS0
│       ├── close_delay
│       ├── closing_wait
│       ├── console
│       ├── custom_divisor
│       ├── dev
│       ├── device -> ../../../00:03:0.0
│       ├── flags
│       ├── iomem_base
│       ├── iomem_reg_shift
│       ├── io_type
│       ├── irq
│       ├── line
│       ├── port
│       ├── power
│       │   ├── async
│       │   ├── autosuspend_delay_ms
│       │   ├── control
│       │   ├── runtime_active_kids
│       │   ├── runtime_active_time
│       │   ├── runtime_enabled
│       │   ├── runtime_status
│       │   ├── runtime_suspended_time
│       │   ├── runtime_usage
│       │   ├── wakeup
│       │   ├── wakeup_abort_count
│       │   ├── wakeup_active
│       │   ├── wakeup_active_count
│       │   ├── wakeup_count
│       │   ├── wakeup_expire_count
│       │   ├── wakeup_last_time_ms
│       │   ├── wakeup_max_time_ms
│       │   └── wakeup_total_time_ms
│       ├── rx_trig_bytes
│       ├── subsystem -> ../../../../../../../class/tty
│       ├── type
│       ├── uartclk
│       ├── uevent
│       └── xmit_fifo_size
└── uevent


CODE
bool isTtyS = entry.Name.StartsWith("ttyS", StringComparison.Ordinal);
bool isTtyGS = !isTtyS && entry.Name.StartsWith("ttyGS", StringComparison.Ordinal);
if ((isTtyS &&
            (File.Exists(entry.FullName + "/device/id") ||
            Directory.Exists(entry.FullName + "/device/of_node"))) ||
      (!isTtyS && Directory.Exists(entry.FullName + "/device/tty")) ||
      Directory.Exists(sysUsbDir + entry.Name) ||
      (isTtyGS && (File.Exists(entry.FullName + "/dev")))) {
      
      ...
      
}


За isTtyS се търси id или of_node които липсват. Ако мина в not isTtyS или isTtyGS проверките ще минат (но и двете са на база име в /sys но там пипа само ядрото). Друг вариант за да мине проверката е да се провали проверката за това дали sysTtyDir е налична.

Трети вариант е репорт на бъг или да си билдна сам платформата...

Давайте идеи.

Публикувано от: relax4o 30-10-2025, 18:09
Дори и да репортнеш бъг ще паднат едни обяснения с тях, ще искат код, някакъв начин да се репликейтне, от там да решат заслужава ли си фикс и какъв да е той и накрая ще стане едно нищо. Не ти пречи да го направиш паралелно с друго решение, което вземеш, но иначе ще си чакаш.

Ако ти се занимава, можеш да пробваш тази библиотека, която имплементира libudev https://www.nuget.org/packages/Dandy.Linux.Udev/1.0.0-b2. Стара е и неподдържана, но ме съмнява нещо да се е променило значително, за да не работи.

Какъв е проблема да модифицираш метода за твоите нужди?

Публикувано от: thrawn 30-10-2025, 18:32
Казусът е, че имам приложение на трета страна което трябва да тръгне. За да видя какво не му е на ред, правя отделно приложение само за тестове. Затова търся решение или на база платформа или на база ОС. Вариант с редакция на самото приложение няма.

Публикувано от: relax4o 31-10-2025, 11:34
Ако си сигурен, че лежи на System.IO.Ports, то можеш да пробваш да модифицираш dll на това асембли и презапишеш модифицирана версия на метода. Можеш да пробваш с https://github.com/dnSpyEx/dnSpy, но нещата стават леко hacky.

Публикувано от: thrawn 31-10-2025, 13:43
Бота предлага да направя собствен модул за ядрото който да създаде липсващите елементи. Тия дни обаче имам други задачи и тоя проект ще го оставя за другата седмица.

Така ми се струва, че ще е най-чисто а и ще се поучи универсален фикс.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)