Recent

Author Topic: TLazSerial : serial port component for Lazarus (windows and linux).  (Read 391099 times)

tetrastes

  • Hero Member
  • *****
  • Posts: 694
Re: TLazSerial : serial port component for Lazarus (windows and linux).
« Reply #465 on: December 05, 2024, 12:26:29 pm »
Actually, I am not sure if LazSerial1.FlowControl := fcHardware; is usable at all, it seems to me it does too many things that it should not do.
Sincerely, I know nothing about Serial communication so I might be wrong, but I have compared the behaviour of several other serial terminals and they do not behave this way.
It sets RTS/CTS handshaking flow control, nothing else.
If you set other terminals to this flow control, they should behave the same way.
« Last Edit: December 05, 2024, 12:30:28 pm by tetrastes »

CM630

  • Hero Member
  • *****
  • Posts: 1527
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: TLazSerial : serial port component for Lazarus (windows and linux).
« Reply #466 on: December 06, 2024, 03:30:51 pm »
I am trying to set the most common configuration (see image attached).
But I cannot find a way to set XonXoff_and_DTR_DSR. I found no way to set SERIAL_RTS_CONTROL to 1. Any clue about that?


The other settings seem to be fine, but I intend to recheck once or twice again.


Code: Pascal  [Select][+][-]
  1.  
  2. type
  3.   THandFlow = (hfNone,hfXonXoff,hfRTS_CTS,hfXonXoff_and_RTS_CTS,hfDTR_DSR,hfXonXoff_and_DTR_DSR);
  4.  
  5.  
  6. ....
  7.  
  8.  
  9. procedure SetHandFlow(HandFlow: THandFlow; var aLazSerial: TLazSerial);
  10. begin
  11.   with aLazSerial.SynSer.DCB do
  12.   begin
  13.     Flags := dcb_Binary; //$00000001;
  14.     case HandFlow of
  15.       hfNone    : Flags := Flags and not dcb_RtsControlEnable;
  16.       hfXonXoff : begin Flags := Flags or dcb_OutX or dcb_InX; Flags := Flags or dcb_DtrControlEnable; end;
  17.       hfDTR_DSR : Flags := Flags or dcb_DtrControlEnable;
  18.       hfRTS_CTS : Flags := Flags or dcb_OutxCtsFlow or dcb_RtsControlHandshake; //aka Hardware
  19.       hfXonXoff_and_RTS_CTS : begin Flags := Flags or dcb_OutX or dcb_InX; Flags := Flags or dcb_DtrControlEnable; Flags := Flags or dcb_OutxCtsFlow or dcb_RtsControlHandshake; end;
  20.       hfXonXoff_and_DTR_DSR : begin {Flags := Flags or dcb_DtrControlHandshake; Flags := Flags or dcb_OutX or dcb_InX {or dcb_DtrControlMask} or $22;} Flags := Flags or $322  end;
  21.     end; //case
  22.   end; //with
  23.   aLazSerial.SynSer.SetCommState;
  24. end;  
  25.  

Лазар 4,2 32 bit (sometimes 64 bit); FPC3,2,2

mas steindorff

  • Hero Member
  • *****
  • Posts: 560
Re: TLazSerial : serial port component for Lazarus (windows and linux).
« Reply #467 on: December 06, 2024, 10:05:31 pm »
if your using windows ...
SetDTRf(value)
GetDSR()

not sure about if the library automatically handles the hold and release chars
windows 10 &11, Ubuntu 21+ IDE 3.4 general releases

tetrastes

  • Hero Member
  • *****
  • Posts: 694
Re: TLazSerial : serial port component for Lazarus (windows and linux).
« Reply #468 on: December 06, 2024, 10:20:10 pm »
I am trying to set the most common configuration (see image attached).
Your image looks weird to me. You confused with terminology. F.e., DTR_CONTROL is the name of line (signal). It can be set to one of three states: DTR_CONTROL_DISABLE (0), DTR_CONTROL_ENABLE (1), and DTR_CONTROL_HANDSHAKE (auto switching). You have to read about RS232 control lines and flow controls, and this https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-dcb.

But I cannot find a way to set XonXoff_and_DTR_DSR. I found no way to set SERIAL_RTS_CONTROL to 1. Any clue about that?

Code: Pascal  [Select][+][-]
  1. port.Config(baud, bits, parity, stop, true, false);      // set Xon/Xoff and enable RTS control line (dcb.Flags := dcb.Flags or dcb_RtsControlEnable;). Also note that in port.Connect() there is RTS:=True already.        
  2. port.GetCommState;
  3. port.dcb.Flags := port.dcb.Flags and not (dcb_DtrControlEnable or dcb_DsrSensivity);
  4. port.dcb.Flags := port.dcb.Flags or dcb_OutxDsrFlow or dcb_DtrControlHandshake;    // add DTR/DSR handshaking
  5. port.SetCommState;
  6.  


« Last Edit: December 06, 2024, 10:45:31 pm by tetrastes »

tetrastes

  • Hero Member
  • *****
  • Posts: 694
Re: TLazSerial : serial port component for Lazarus (windows and linux).
« Reply #469 on: December 06, 2024, 10:33:38 pm »
if your using windows ...
SetDTRf(value)
GetDSR()

These functions are protected, you cannot use them directly. They are for properties
Code: Pascal  [Select][+][-]
  1.     {:Use this property to set the value of the DTR signal.}
  2.     property DTR: Boolean write SetDTRF;
  3.  
  4.     {:Exposes the status of the DSR signal.}
  5.     property DSR: boolean read GetDSR;
  6.  
  7. . . .
  8.     port.DTR := false;
  9.     boolVar := port.DSR;

And they these properties can be used not only in windows.
« Last Edit: December 06, 2024, 10:41:12 pm by tetrastes »

CM630

  • Hero Member
  • *****
  • Posts: 1527
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: TLazSerial : serial port component for Lazarus (windows and linux).
« Reply #470 on: December 09, 2024, 12:39:36 pm »

Your image looks weird to me. You confused with terminology. F.e., DTR_CONTROL is the name of line (signal). It can be set to one of three states: DTR_CONTROL_DISABLE (0), DTR_CONTROL_ENABLE (1), and DTR_CONTROL_HANDSHAKE (auto switching). You have to read about RS232 control lines and flow controls, and this https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-dcb.
That is what the sniffer shows. My cunning plan is to make those routines once and forget about them. Anyway, here https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddser/ns-ntddser-_serial_handflow the listed values are SERIAL_DTR_CONTROL and SERIAL_DTR_HANDSHAKE. But I assume SERIAL_DTR_CONTROL  and SERIAL_DTR_HANDSHAKE cannot both be equal to zero at the same time.


Your help worked, I have now:


Code: Pascal  [Select][+][-]
  1. procedure SetHandFlow(HandFlow: THandFlow; var aLazSerial: TLazSerial);
  2. begin
  3.   with aLazSerial.SynSer.DCB do
  4.   begin
  5.     Flags := dcb_Binary; //$00000001;
  6.     case HandFlow of
  7.       hfNone    : Flags := Flags and not dcb_RtsControlEnable;
  8.       hfXonXoff : begin Flags := Flags or dcb_OutX or dcb_InX; Flags := Flags or dcb_DtrControlEnable; end;
  9.       hfDTR_DSR : Flags := Flags or dcb_DtrControlEnable;
  10.       hfRTS_CTS : Flags := Flags or dcb_OutxCtsFlow or dcb_RtsControlHandshake; //aka Hardware
  11.       hfXonXoff_and_RTS_CTS : begin Flags := Flags or dcb_OutX or dcb_InX; Flags := Flags or dcb_DtrControlEnable; Flags := Flags or dcb_OutxCtsFlow or dcb_RtsControlHandshake; end;
  12.       hfXonXoff_and_DTR_DSR : begin Flags := Flags or dcb_DtrControlHandshake or dcb_OutxDsrFlow or dcb_OutX or dcb_InX or dcb_RtsControlToggle; Flags := Flags and not dcb_RtsControlHandshake; end;
  13.     end; //case
  14.   end; //with
  15.   aLazSerial.SynSer.SetCommState;
  16. end;
  17.  
   




But it seems that everyone has a different idea about what DTR/DSR shall be.
I sniffed several terminal apps, two of them set it as:
SERIAL_DTR_HANDSHAKE = 1; SERIAL_DSR_HANDSHAKE =1; SERIAL_RTS_CONTROL = 1; everything else = 0.


One of them sets different values each time.
And another one sets SERIAL_DTR_CONTROL = 1; SERIAL_CTS_HANDSHAKE = 1; everything else = 0.


In order to connect to the multimeter I need SERIAL_DTR_CONTROL = 1; SERIAL_CTS_HANDSHAKE = *; everything else = 0;
Most terminal apps do not have this mode (once is named DTR/DSR, once is RS485, the multimeter is actually a regular RS232 on 9V).
« Last Edit: December 09, 2024, 01:27:02 pm by CM630 »
Лазар 4,2 32 bit (sometimes 64 bit); FPC3,2,2

tetrastes

  • Hero Member
  • *****
  • Posts: 694
Re: TLazSerial : serial port component for Lazarus (windows and linux).
« Reply #471 on: December 09, 2024, 03:12:30 pm »
Anyway, here https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddser/ns-ntddser-_serial_handflow the listed values are SERIAL_DTR_CONTROL and SERIAL_DTR_HANDSHAKE.
This link is for drivers writers. Synaser doesn't use this. But the sense of fields is the same. If you want to use this, you are on your own. First of all you have to understand how RS-232 lines work.

Quote
But I assume SERIAL_DTR_CONTROL  and SERIAL_DTR_HANDSHAKE cannot both be equal to zero at the same time.
Of course they can, because the whole member ControlHandShake can be zero: "This member is set to zero or to the bitwise-OR or one or more of the following flags." from your link.

Quote
But it seems that everyone has a different idea about what DTR/DSR shall be.
I sniffed several terminal apps, two of them set it as:
SERIAL_DTR_HANDSHAKE = 1; SERIAL_DSR_HANDSHAKE =1; SERIAL_RTS_CONTROL = 1; everything else = 0.
This is what I have written you.

Quote
And another one sets SERIAL_DTR_CONTROL = 1; SERIAL_CTS_HANDSHAKE = 1; everything else = 0.
Code: Pascal  [Select][+][-]
  1. port.Config(baud, bits, parity, stop, false, false);
  2. // RTS and DTR are set at this moment
  3. port.RTS := false;    // as everything else = 0  
  4. port.GetCommState;
  5. port.dcb.Flags := port.dcb.Flags and not dcb_RtsControlEnable;   // as everything else = 0
  6. port.dcb.Flags := port.dcb.Flags or dcb_OutxCtsFlow;  
  7. port.SetCommState;
  8.  
This is something not standard, and of course it is not DTR/DSR handshaking.

Quote
In order to connect to the multimeter I need SERIAL_DTR_CONTROL = 1; SERIAL_CTS_HANDSHAKE = *; everything else = 0;
Are you sure that RTS must be OFF? If not, and "*" means "do not care", simply
Code: Pascal  [Select][+][-]
  1. port.Config(baud, bits, parity, stop, false, false);
otherwise use the above.

CM630

  • Hero Member
  • *****
  • Posts: 1527
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: TLazSerial : serial port component for Lazarus (windows and linux).
« Reply #472 on: December 09, 2024, 04:05:20 pm »
...
Quote
But I assume SERIAL_DTR_CONTROL  and SERIAL_DTR_HANDSHAKE cannot both be equal to zero at the same time.
Of course they can, because the whole member ControlHandShake can be zero: "This member is set to zero or to the bitwise-OR or one or more of the following flags." from your link.
...
Ooops, I meant 1, but wrote 0. %)
Лазар 4,2 32 bit (sometimes 64 bit); FPC3,2,2

CM630

  • Hero Member
  • *****
  • Posts: 1527
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: TLazSerial : serial port component for Lazarus (windows and linux).
« Reply #473 on: May 28, 2025, 03:51:59 pm »
I am trying to build a Windows app to Linux (Mint Mate).
First I did a very simple app and it works (gets data from the serial port).
But the other app throws an error "Threads not supported" on LazSerial.pas:


Code: Pascal  [Select][+][-]
  1.  procedure TLazSerial.DeviceOpen;
  2. begin
  3. ...
  4.    ReadThread := TComPortReadThread.Create(true);
  5.   ...
  6. end;    

Any idea what might be wrong?


Note: Atfter plugging the serial device into USB I type sudo chmod -R 777 /dev/ttyUSB0 in the console.
This is the app that works:

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, LazSerial,lazsynaser;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Button2: TButton;
  16.     LazSerial1: TLazSerial;
  17.     Memo1: TMemo;
  18.     procedure Button1Click(Sender: TObject);
  19.     procedure Button2Click(Sender: TObject);
  20.     procedure FormCreate(Sender: TObject);
  21.     procedure LazSerial1RxData(Sender: TObject);
  22.   private
  23.  
  24.   public
  25.  
  26.   end;
  27.  
  28. var
  29.   Form1: TForm1;
  30.  
  31. implementation
  32.  
  33. {$R *.lfm}
  34.  
  35. { TForm1 }
  36.  
  37. //sudo chmod -R 777 /dev/ttyUSB0
  38.  
  39. procedure TForm1.Button1Click(Sender: TObject);
  40. begin
  41.  
  42. end;
  43.  
  44. procedure TForm1.Button2Click(Sender: TObject);
  45. begin
  46.   //LazSerial1.ShowSetupDialog;
  47.   LazSerial1.Active := True;
  48. end;
  49.  
  50. procedure TForm1.FormCreate(Sender: TObject);
  51. begin
  52.     memo1.Clear ;
  53.    memo1.Append ( GetSerialPortNames);
  54. end;
  55.  
  56. function decodestring(aValue: String): string;
  57. var
  58.   i: integer;
  59. begin
  60.    if avalue = '' then exit('');
  61.   for i:= 1 to length(avalue) do
  62.     Result := Result + ( IntToHex(ord(aValue[i]))) + ' ';
  63. end;
  64.  
  65. procedure TForm1.LazSerial1RxData(Sender: TObject);
  66. var
  67.   recdata: string;
  68. begin
  69.   recdata := LazSerial1.ReadData;
  70.   memo1.Append (decodestring(recdata));
  71.   memo1.SelStart := Length(memo1.Text);
  72.   Application.ProcessMessages;
  73. end;
  74.  
  75. end.

Both apps use the same settings of the serial connection.
I think the app that I am trying to build in Linux does not use multi threading, a least the word "Thread" does not occur in the source code.
« Last Edit: May 28, 2025, 04:03:29 pm by CM630 »
Лазар 4,2 32 bit (sometimes 64 bit); FPC3,2,2

cdbc

  • Hero Member
  • *****
  • Posts: 2467
    • http://www.cdbc.dk
Re: TLazSerial : serial port component for Lazarus (windows and linux).
« Reply #474 on: May 28, 2025, 05:03:08 pm »
Hi
The problem might lie in your *.lpr file...
On Linux & Unix, it's necessary to compile in the 'cthreads' unit as one of the first ref'ed units...
Something like this:
Code: Pascal  [Select][+][-]
  1. uses
  2.   {$IFDEF UNIX}
  3.   cthreads,
  4.   {$ENDIF}
  5.   classes, ....
It's usually present in the auto-generated files from Lazarus...
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6 -> FPC 3.2.2 -> Lazarus 4.0 up until Jan 2025 from then on it's both above &: KDE6/QT6 -> FPC 3.3.1 -> Lazarus 4.99

CM630

  • Hero Member
  • *****
  • Posts: 1527
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: TLazSerial : serial port component for Lazarus (windows and linux).
« Reply #475 on: May 29, 2025, 08:40:55 am »
...
The problem might lie in your *.lpr file...
...
EXACTLY!
In the LPR file this was missing:

Code: Pascal  [Select][+][-]
  1. uses
  2.   {$IFDEF UNIX}
  3.   cthreads,
  4.   {$ENDIF}
  5.   classes, ....
while it existed in the newly created snippet.

The app was originally written in Delphi, AFAIR some things in the LP* files went wrong during the conversion, yet another one loomed yesterday.
So I added the missing lines, now it works (so far).
Лазар 4,2 32 bit (sometimes 64 bit); FPC3,2,2

CM630

  • Hero Member
  • *****
  • Posts: 1527
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: TLazSerial : serial port component for Lazarus (windows and linux).
« Reply #476 on: June 04, 2025, 01:37:51 pm »
How can I detect that a device is (un)plugged in Linux?
Shall I periodically (with a timer) verify if the serial device is mounted (/dev/ttyUSBx) or could I use some kind of an event?
Maybe periodically updating GetSerialPortNames is better than if fileexists('/dev/ttyUSBx')?

In Windows I use:

Code: Pascal  [Select][+][-]
  1. TfrmMain = class (TForm)
  2. ...
  3.   private
  4.     procedure WMPnPDeviceChange(var Message: TMessage); message WM_DEVICECHANGE;
  5. ...
  6. procedure TfrmMain.WMPnPDeviceChange(var Message: TMessage);
  7. begin
  8.   if (Message.WParam = DBT_DEVNODES_CHANGED) then
  9.   begin
  10.     bPnP_DevNodeChange := true;
  11.     bPnP_Removal := false;
  12.   end;  
  13. ...
  14. end;
  15. ...
Лазар 4,2 32 bit (sometimes 64 bit); FPC3,2,2

alpine

  • Hero Member
  • *****
  • Posts: 1410
Re: TLazSerial : serial port component for Lazarus (windows and linux).
« Reply #477 on: June 04, 2025, 02:45:00 pm »
How can I detect that a device is (un)plugged in Linux?
Shall I periodically (with a timer) verify if the serial device is mounted (/dev/ttyUSBx) or could I use some kind of an event?
Maybe periodically updating GetSerialPortNames is better than if fileexists('/dev/ttyUSBx')?
The trouble is the file won't disappear while it is open.
 
Try
Code: Pascal  [Select][+][-]
  1. if FpStat(FileName, Sta) = 0 then
  2.       if Sta.st_ctime = 0 then {disconnected};

but you should do some mapping to ensure same USB device will always be mapped to the same file name.
ie. in /etc/udevadm/rules.d/99-local.rules include something (as appropriate):
Code: Text  [Select][+][-]
  1. SUBSYSTEM=="tty", KERNELS=="1-1:1.0", ENV{ID_MODEL}=="USB-COM232_Plus4", SYMLINK+="ftdi_0_0"
  2. SUBSYSTEM=="tty", KERNELS=="1-1:1.1", ENV{ID_MODEL}=="USB-COM232_Plus4", SYMLINK+="ftdi_0_1"
  3. SUBSYSTEM=="tty", KERNELS=="1-1:1.2", ENV{ID_MODEL}=="USB-COM232_Plus4", SYMLINK+="ftdi_0_2"
  4. SUBSYSTEM=="tty", KERNELS=="1-1:1.3", ENV{ID_MODEL}=="USB-COM232_Plus4", SYMLINK+="ftdi_0_3"
etc.

« Last Edit: June 04, 2025, 02:52:56 pm by alpine »
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

MarkMLl

  • Hero Member
  • *****
  • Posts: 8505
Re: TLazSerial : serial port component for Lazarus (windows and linux).
« Reply #478 on: June 04, 2025, 02:45:27 pm »
How can I detect that a device is (un)plugged in Linux?

You can poll the name, or alternatively there's vestigial netlink support in https://github.com/MarkMLl/serialcomms hence prepareUdevSocket() etc. in https://github.com/MarkMLl/Mastech_ms2115b/blob/main/serialcomms/trunk/locateports.pas

/In/ /principle/, it should be possible to not only have device hotpluggability (which works) but also to simulate hotplug events to handle those devices which are present at program startup (which I've not done).

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Thaddy

  • Hero Member
  • *****
  • Posts: 18356
  • Here stood a man who saw the Elbe and jumped it.
Re: TLazSerial : serial port component for Lazarus (windows and linux).
« Reply #479 on: June 04, 2025, 03:40:57 pm »
@CM630
If you install libudev-dev you can use this unit. Add a demo later.
Code: Pascal  [Select][+][-]
  1. unit USBDetectLinux;
  2. {$mode objfpc}{$H+}
  3.  
  4. interface
  5.  
  6. uses
  7.   Classes, SysUtils, Unix, BaseUnix, CTypes;
  8.  
  9. type
  10.   TUSBEventMethod = procedure(Arrival: Boolean; const DevicePath: String) of object;
  11.   TUSBEventProc = procedure(Arrival: Boolean; const DevicePath: String);
  12.  
  13.   { TUdevMonitorThread }
  14.  
  15.   TUdevMonitorThread = class(TThread)
  16.   private
  17.     FUdev: Pointer;
  18.     FMonitor: Pointer;
  19.     FOnUSBEventMethod: TUSBEventMethod;
  20.     FOnUSBEventProc: TUSBEventProc;
  21.     procedure HandleDeviceEvent(dev: Pointer);
  22.   protected
  23.     procedure Execute; override;
  24.   public
  25.     constructor Create(CreateSuspended: Boolean);
  26.     destructor Destroy; override;
  27.    
  28.     property OnUSBEvent: TUSBEventMethod read FOnUSBEventMethod write FOnUSBEventMethod;
  29.     property OnUSBEventProc: TUSBEventProc read FOnUSBEventProc write FOnUSBEventProc;
  30.   end;
  31.  
  32. implementation
  33.  
  34. const
  35.   libudev = 'libudev.so.1';
  36.  
  37. type
  38.   Pudev = Pointer;
  39.   Pudev_device = Pointer;
  40.   Pudev_monitor = Pointer;
  41.  
  42. // Udev functions
  43. function udev_new: Pudev; cdecl; external libudev;
  44. function udev_unref(udev: Pudev): Pudev; cdecl; external libudev;
  45. function udev_monitor_new_from_netlink(udev: Pudev; name: PChar): Pudev_monitor; cdecl; external libudev;
  46. function udev_monitor_filter_add_match_subsystem_devtype(mon: Pudev_monitor; subsystem: PChar; devtype: PChar): cint; cdecl; external libudev;
  47. function udev_monitor_enable_receiving(mon: Pudev_monitor): cint; cdecl; external libudev;
  48. function udev_monitor_get_fd(mon: Pudev_monitor): cint; cdecl; external libudev;
  49. function udev_monitor_receive_device(mon: Pudev_monitor): Pudev_device; cdecl; external libudev;
  50. function udev_device_get_devnode(dev: Pudev_device): PChar; cdecl; external libudev;
  51. function udev_device_get_action(dev: Pudev_device): PChar; cdecl; external libudev;
  52. function udev_device_get_property_value(dev: Pudev_device; key: PChar): PChar; cdecl; external libudev;
  53. function udev_device_get_subsystem(dev: Pudev_device): PChar; cdecl; external libudev;
  54. function udev_device_unref(dev: Pudev_device): Pudev_device; cdecl; external libudev;
  55.  
  56. { TUdevMonitorThread }
  57.  
  58. constructor TUdevMonitorThread.Create(CreateSuspended: Boolean);
  59. begin
  60.   inherited Create(CreateSuspended);
  61.   FreeOnTerminate := False;
  62.   FUdev := udev_new();
  63.   if not Assigned(FUdev) then
  64.     raise Exception.Create('Failed to initialize udev');
  65. end;
  66.  
  67. destructor TUdevMonitorThread.Destroy;
  68. begin
  69.   if Assigned(FMonitor) then
  70.     udev_unref(FMonitor);
  71.   if Assigned(FUdev) then
  72.     udev_unref(FUdev);
  73.   inherited Destroy;
  74. end;
  75.  
  76. procedure TUdevMonitorThread.HandleDeviceEvent(dev: Pointer);
  77. var
  78.   action, devnode, subsystem, idBus: PChar;
  79.   isUSB, isSerial: Boolean;
  80. begin
  81.   if not Assigned(dev) then Exit;
  82.  
  83.   action := udev_device_get_action(dev);
  84.   devnode := udev_device_get_devnode(dev);
  85.  
  86.   if not (Assigned(action) and Assigned(devnode)) then Exit;
  87.  
  88.   subsystem := udev_device_get_subsystem(dev);
  89.   idBus := udev_device_get_property_value(dev, 'ID_BUS');
  90.  
  91.   // Check if it's a USB device
  92.   isUSB := (idBus <> nil) and (idBus = 'usb');
  93.  
  94.   // Check if it's a serial device (FIXED: added missing parenthesis)
  95.   isSerial := (subsystem <> nil) and (subsystem = 'tty') and
  96.               (udev_device_get_property_value(dev, 'ID_USB_INTERFACE_NUM') <> nil);
  97.  
  98.   if isUSB and isSerial then
  99.   begin
  100.     if action = 'add' then
  101.     begin
  102.       if Assigned(FOnUSBEventMethod) then FOnUSBEventMethod(True, devnode);
  103.       if Assigned(FOnUSBEventProc) then FOnUSBEventProc(True, devnode);
  104.     end
  105.     else if action = 'remove' then
  106.     begin
  107.       if Assigned(FOnUSBEventMethod) then FOnUSBEventMethod(False, devnode);
  108.       if Assigned(FOnUSBEventProc) then FOnUSBEventProc(False, devnode);
  109.     end;
  110.   end;
  111.  
  112.   // Free the device reference
  113.   udev_device_unref(dev);
  114. end;
  115.  
  116. procedure TUdevMonitorThread.Execute;
  117. var
  118.   fd: cint;
  119.   fds: TFDSet;
  120.   res: cint;
  121.   dev: Pudev_device;
  122.   timeout: TTimeVal;
  123. begin
  124.   FMonitor := udev_monitor_new_from_netlink(FUdev, 'udev');
  125.   if not Assigned(FMonitor) then Exit;
  126.  
  127.   // Filter for USB devices and tty subsystem
  128.   udev_monitor_filter_add_match_subsystem_devtype(FMonitor, 'usb', nil);
  129.   udev_monitor_filter_add_match_subsystem_devtype(FMonitor, 'tty', nil);
  130.  
  131.   if udev_monitor_enable_receiving(FMonitor) <> 0 then
  132.   begin
  133.     udev_unref(FMonitor);
  134.     FMonitor := nil;
  135.     Exit;
  136.   end;
  137.  
  138.   fd := udev_monitor_get_fd(FMonitor);
  139.  
  140.   while not Terminated do
  141.   begin
  142.     fpFD_ZERO(fds);
  143.     fpFD_SET(fd, fds);
  144.    
  145.     // 1 second timeout
  146.     timeout.tv_sec := 1;
  147.     timeout.tv_usec := 0;
  148.    
  149.     res := fpSelect(fd + 1, @fds, nil, nil, @timeout);
  150.    
  151.     // Handle errors (including EINTR)
  152.     if res < 0 then
  153.     begin
  154.       if fpgeterrno = ESysEINTR then
  155.         Continue
  156.       else
  157.         Break;
  158.     end;
  159.    
  160.     if res = 0 then Continue; // Timeout
  161.    
  162.     if fpFD_ISSET(fd, fds) <> 0 then
  163.     begin
  164.       dev := udev_monitor_receive_device(FMonitor);
  165.       if Assigned(dev) then
  166.       begin
  167.         HandleDeviceEvent(dev);
  168.       end;
  169.     end;
  170.   end;
  171.  
  172.   if Assigned(FMonitor) then
  173.   begin
  174.     udev_unref(FMonitor);
  175.     FMonitor := nil;
  176.   end;
  177. end;
  178.  
  179. end.
[edit]
Demo:
Code: Pascal  [Select][+][-]
  1. program USBDetectTest;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   {$IFDEF UNIX}
  7.   cthreads,   // REQUIRED for threading on Linux
  8.   {$ENDIF}
  9.   Classes, SysUtils, USBDetectLinux;
  10.  
  11. // Standalone procedure
  12. procedure USBEventHandler(Arrival: Boolean; const DevicePath: String);
  13. begin
  14.   if Arrival then
  15.     WriteLn('Device connected: ', DevicePath)
  16.   else
  17.     WriteLn('Device disconnected: ', DevicePath);
  18. end;
  19.  
  20. // Object method
  21. type
  22.   TMyUSBMonitor = class
  23.     procedure USBEventMethod(Arrival: Boolean; const DevicePath: String);
  24.   end;
  25.  
  26. procedure TMyUSBMonitor.USBEventMethod(Arrival: Boolean; const DevicePath: String);
  27. begin
  28.   if Arrival then
  29.     WriteLn('Object: Device connected: ', DevicePath)
  30.   else
  31.     WriteLn('Object: Device disconnected: ', DevicePath);
  32. end;
  33.  
  34. var
  35.   MonitorThread: TUdevMonitorThread;
  36.   MonitorObj: TMyUSBMonitor;
  37.  
  38. begin
  39.   MonitorThread := TUdevMonitorThread.Create(True);
  40.   MonitorObj := TMyUSBMonitor.Create;
  41.   try
  42.     // Use the standalone procedure handler
  43.     MonitorThread.OnUSBEventProc := @USBEventHandler;
  44.    
  45.     // Or use the object method handler (choose one)
  46.     // MonitorThread.OnUSBEvent := @MonitorObj.USBEventMethod;
  47.    
  48.     MonitorThread.Start;
  49.    
  50.     WriteLn('Monitoring USB serial devices. Press Enter to exit...');
  51.     ReadLn;
  52.    
  53.     MonitorThread.Terminate;
  54.     MonitorThread.WaitFor;
  55.   finally
  56.     MonitorThread.Free;
  57.     MonitorObj.Free;
  58.   end;
  59. end.

This is only to detect insert/remove USB serial devices! but the code can be easily adapted for others. All the basics are there.
BTW I run this in a separate group with elevated rights.
When testing, run as sudo.

(I got a little help from deepseek here, specifically debugging, but plenty of this is my own code)

« Last Edit: June 04, 2025, 03:51:22 pm by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

 

TinyPortal © 2005-2018