♫ ♩ ♪ Nachtblut ⚡ ☘ ⚡ Antik♪ ♩ ♫
https://youtu.be/4msVueJK_6I
Swego czasu na łamach Elektroniki dla Wszystkich pozwoliłam sobie na stwierdzenie, że jak do czegoś dają kod źródłowy to nie tylko wolno, ale wręcz trzeba coś w nim dłubnąć. Zwyczaj patroszenia nowych zabawek został mi z dzieciństwa, stąd dorwawszy kawałek fajnej terminalowej aplikacji w Pascal nie omieszkałam zbudować programiku na Linux, a potem wściubić nos w środek. No i jest tak...
multi-kulti
Środowisko Lazarus IDE (jak i działający w tle Free Pascal) tworzone są pod hasłem - napisz raz, kompiluj wszędzie. No super takie ideały są i w znakomitej większości przypadków fakt skompilowania się kodu zachodzi i w Windows i w Linux. Z działaniem jest deko gorzej, ponieważ nie ma cudów - są funkcjonalności mocno wykorzystujące specyficzne cechy systemu operacyjnego, a im bliżej schodzimy sprzętu, tym prawdopodobieństwo ich spotkania jakby większe.
Do takich właśnie zagadek należy temat listy wyboru nazwy portu szeregowego przy uruchamianiu czy konfigurowaniu aplikacji. Czasy wbudowanych w maszynę portów COM zaczynają odchodzić w przeszłość, królują wszelkie wtykalne przelotki i inne cuda na USB, dla użytkownika oznacza to, że lista portów nie jest stała, zmienia się zarówno ilościowo jak i dostępnym spektrum nazw.
A to z kolei oznacza, że aplikacja powinna wspomóc użytkownika i wylistować aktualnie dostępne porty, niech sobie biedny człowiek wybierze.
W środowisku Lazarus, w całkiem fajnych klockach synapse sprawę rozwiązano przygotowując funkcję GetSerialPortNames, implementacja objęta jest kompilacją warunkową. Wersja dla Windows bazuje na rejestrze, wersja dla Linux listuje zasoby kartoteki /dev. Jeden z wariantów dostępnych w sieci znajdziemy w pliku: https://github.com/marado/synapse/blob/ ... .pas#L2320
Wskazuję implementację linuksową, ponieważ do niej miałam pewne zastrzeżenia, w szczególności - EAccessViolation w przypadkowych momentach, stąd nieodparta potrzeba napisania własnego rozwiązania, które przy okazji przetestowałam na podesłanej aplikacji.
Środowisko sprzętowe to dwie przelotki RS232/USB na układach Prolific, oryginalne Arduino oraz Ardu z Chinowa z układem CH341, do tego Hub USB.
Dla Windows - całość pracuje całkiem sensownie - lista COM zgadza się z tym co wypisuje Manager Urządzeń.
Dla Linux, po drobnych zabiegach też wszystko działa jak należy:
łatka szeregowa
I proszę, oto moja wieczorna wizja, jak załadować rozwijaną listę wyboru (combobox) kolejnymi nazwami portów szeregowych. Przyjęłam, że pokazywane mają być porty z płyty (ttySnn), kabelki USB (ttyUSBnn) oraz wszelkie autorskie szeregowe interfejsy znane z Ardu i niektórych modemów GSM czyli ttyACMnn. Btw, ciekawe info o różnicach w dwóch ostatnich znajdziemy tu: https://rfc1149.net/blog/2013/03/05/wha ... evttyacmx/
Funkcyjna pomocnicza sprawdzająca faktyczną dostępność wskazanego nazwą portu:
Kod: Zaznacz cały
{$IFDEF LINUX}
function IsPortAvailable ( portName : string ) : boolean;
var
aPort : TBlockSerial;
begin
aPort := TBlockSerial.Create;
aPort.Connect( portName );
IsPortAvailable := aPort.LastError = 0;
aPort.Free; // spoczko, destruktor robi close
end;
{$ENDIF}
Funkcja budująca listę dostępnych do wykorzystania portów:
Kod: Zaznacz cały
{$IFDEF LINUX}
function GetSerialPortNamesEx : string;
var
serialPorts : TStringList;
i : integer;
res : string;
begin
res := '';
serialPorts := TStringList.Create;
try
FindAllFiles( serialPorts, '/dev', 'ttyS*;ttyUSB*;ttyACM*', false );
for i := 0 to serialPorts.Count-1 do
begin
if IsPortAvailable( serialPorts.Strings[i] ) then
begin
if length( res ) <> 0 then res := res + ',';
res := res + serialPorts.Strings[i];
end;
end;
finally
serialPorts.Free;
end;
GetSerialPortNamesEx := res;
end;
{$ENDIF}
Powyższe zwróci nam te nazwy, które możemy zaproponować w danej chwili użytkownikowi do wyboru, jest oczywiste, że lista może zdarzyć się pusta, musimy sobie i z tym poradzić.
Ładowanie nazw portów do listy najłatwiej zrealizować w metodzie FormCreate() okna głównego (lub dialogu konfiguracyjnego), moja wielokulturowa propozycja jest taka:
Kod: Zaznacz cały
procedure TMain.FormCreate ( Sender : TObject ) ;
var serialPorts : string;
begin
{$IFNDEF LINUX}
{$NOTE windowsowe GetSerialPortNames }
serialPorts := GetSerialPortNames;
{$ELSE}
{$NOTE wlasna implementacja GetSerialPortNamesEx }
serialPorts := GetSerialPortNamesEx;
{$ENDIF}
if serialPorts = '' then with Application do
begin
MessageBox( 'Brak wolnych portów szeregowych.','oops!', MB_OK + MB_ICONEXCLAMATION );
Terminate;
exit;
end;
// tu zawsze choć jeden element, jest bezpiecznie
SerialPortComboBox.Items.CommaText := serialPorts;
SerialPortComboBox.Text := SerialPortComboBox.Items[ 0 ];
//... dalsza inicjalizacja kontrolek
end;
Dodanie powyższego nie wymaga specjalnych ceregieli, jedynie co to unit LCLType w sekcji uses - w nim są stałe MB_OK i reszta.
Łatka w praktyce:
Jak widać, w przypadku niemożności zaproponowania jakiegokolwiek portu - aplikacja zejdzie, więc takie podejście nadaje się do konfigurowania ad-hoc, a nie przygotowywania ustawień off-line, na sucho. Po drugie - test dostępności polega na otwarciu (i automatycznym zamknięciu ) połączenia, nie zawsze możemy sobie na to pozwolić bez konsekwencji w postaci mignięcia sygnałami sterującymi typu DTR, CTS, etc. No i na koniec jedna sprawa, w sumie to nie wiem czemu tak omijana - komunikaty w oknie Messages środowiska. Jest dyrektywa $NOTE, która umożliwia wypisanie w strumieniu komunikatów swej własnej wiadomości na przykład o ścieżce dla kompilacji warunkowej czy przyjętych do budowania stałych etc, to fajna sprawa - na zrzutce ekranu widać i te meldunki.
#slowanawiatr