Some updates done in https://wiki.freepascal.org/Hardware_Access#Serial_Communication
Hello CM360, I’ve noticed that much of the example code in the wiki tends to make sense mainly to more seasoned or experienced developers. For newcomers, it can be confusing and even discouraging when examples
assume prior knowledge.
A few beginner-friendly explanations or simpler examples would make a huge difference in helping new users feel confident and included. Adding a few simpler examples or extra explanations for beginners would really help make the wiki more welcoming and easier to learn from. Some examples are so cryptic and way beyond a newbie level. This is just my two cents, the kind of thing 'I' would have loved to see in the wiki back when I was a beginner and clueless newbie. What follows are not criticisms, but genuine concerns that I’m pointing out along with suggestions on how they could be improved (if you wish and the community agree, of course).
This is the kind of example where a newcomer to Lazarus / Free Pascal on Linux can easily get confused, because at first glance, it looks like Windows COM-port code due to the name
GetComFriendlyName, but it’s actually Linux-specific and relies on the udevadm command-line tool. Rename the function to something clearer, such as:
Function GetLinuxSerialDeviceFriendlyName(PortName: String): String; That immediately signals “Linux-only” and avoids Windows COM confusion.
Here’s the same code, but heavily commented and explained line-by-line, with all the pitfalls and confusing parts for newcomers clearly pointed out:
{$IfDef Linux}
// The following function is *only compiled on Linux systems*.
// This avoids build errors on non-Linux platforms.
Function GetComFriendlyName(PortName: String): String;
var
RunOutput: string = ''; // Will hold the text output from the 'udevadm' command.
StartPos: integer = 0; // Used to locate where the friendly name begins in the output.
EndPos: integer = 0; // Used to locate where the friendly name ends in the output.
begin
Result := '';
// Always initialize Result to an empty string in case the function fails.
// This ensures that callers don’t receive garbage data.
// The following line runs the Linux shell command `udevadm info -q property <device>`
// Example: udevadm info -q property /dev/ttyUSB0
// It stores all the command output text into the variable RunOutput.
//
// ⚠️ POTENTIAL PITFALLS FOR NEWCOMERS:
// - RunCommand is part of the Free Pascal unit `Process`, not `SysUtils`.
// You must include `Process` in your "uses" clause or you’ll get “Identifier not found: RunCommand”.
// - 'udevadm' must exist on your system (it normally does on most Linux systems).
// - If you pass a wrong device name like '/dev/ttyFAKE0', RunCommand still "succeeds"
// but RunOutput may be empty or contain an error message.
if not RunCommand('udevadm info -q property ' + PortName, RunOutput) then
exit;
// If the command fails entirely (returns false), the function exits immediately
// leaving Result = '', which indicates no friendly name could be found.
// Now we search for the text "ID_MODEL_FROM_DATABASE=" inside the output.
// This field usually contains the *human-readable device model name*,
// such as "FT232 USB UART" or "Arduino Uno".
StartPos := Pos('ID_MODEL_FROM_DATABASE=', RunOutput);
if (StartPos = 0) then
exit;
// If that substring is not found, exit — no friendly name is available.
// Move StartPos past the "ID_MODEL_FROM_DATABASE=" label
// so that it points directly to the beginning of the *actual friendly name text*.
StartPos := StartPos + Length('ID_MODEL_FROM_DATABASE=');
// Find the position of the next newline character (#10)
// after the start of the friendly name. This marks where the name ends.
EndPos := Pos(#10, RunOutput, StartPos + 1);
// Copy just the friendly name portion from RunOutput into Result.
Result := Copy(RunOutput, StartPos, EndPos - StartPos);
end;
{$EndIf}
Confusing Aspects for Newcomers
- Function name (GetComFriendlyName) sounds Windows-related The term “COM” is used on Windows (e.g., COM1, COM2). On Linux, serial devices are /dev/ttyUSB0, /dev/ttyACM0, etc. So a beginner might assume this works on Windows, it does not.
- Missing unit in “uses” The function requires Process for RunCommand. If you forget it, you’ll get Identifier not found: RunCommand.
- Doesn’t handle missing or invalid devices gracefully If /dev/ttyUSB0 doesn’t exist, you just get an empty result, no error message. Newcomers might think the code “does nothing.”
- Case sensitivity of Linux /dev/ttyUSB0 ≠ /dev/TTYUSB0. Linux paths are case-sensitive, unlike Windows.
- Command availability udevadm may not be in the user’s PATH on some embedded or minimal systems. The function will silently fail if it’s not installed.
- Hardcoded property name It assumes the field ID_MODEL_FROM_DATABASE exists. Some devices may not have that property (e.g., some cheap adapters). Then you’ll get an empty string.
- Not cross-platform Wrapped in {$IfDef Linux}, so this will not compile or work on Windows or macOS. Beginners might not realize this and try to use it universally.
Suggestions for Beginners
Rename the function to something clearer, such as:
Function GetLinuxSerialDeviceFriendlyName(PortName: String): String;That immediately signals “Linux-only” and avoids Windows COM confusion.
Add error feedback, for example:
if RunOutput = '' then
Result := 'Device not found or udevadm unavailable';
Show debug output during testing:
WriteLn('udevadm output:', LineEnding, RunOutput);
Remember to add Process in your uses clause:
uses SysUtils, Process;
I hope this will be taken in the same spirit it is said in. I have been there, done that, printed the T-shirts. Why make life difficult when together we can learn and build amazing stuff?
And the attached image hopefully will make you all smile :)