ComPort i usunięte urządzenie USB

0

Cześć,

jakiś czas temu napisałem program który między innymi łączy się z prostym wyświetlaczem LCD. Wyświetlacz skonstruowałem na Arduino i podłączam go do komputera kabelkiem USB. Doinstalowuje się w tym czasie wirtualny port COM (sterowniki od Arduino).

Wszystko działa idealnie gdy istnieje port COM, nawet start programu bez portu COM nie robi problemów, po prostu brak tego portu na starcie aplikacji wyłącza tą funkcjonalność.

Problem jest bardzo duży gdy port COM istnieje w trakcie startu programu a później znika, niestety jako że jest to USB a obsługa jest zdolna do wszystkiego takie coś może się zdarzyć.
Dostaję wtedy wyjątek który jest nie do "ubicia". Jedyne wyjście to zabicie procesu w Menadżerze zadań co jest zbyt skomplikowane dla obsługi - oni po prostu wyłączają komputer.

Konkrety, używam komponentu ComPort Library https://sourceforge.net/projects/comport/
Problem jest znany ale nie do końca rozwiązany. Większość rozwiązań sugeruje lekką modyfikację kodu w źródle komponentu a konkretnie w procedurze TCustomComPort.AbortAllAsync.

Rzeczywiście, taka podmiana powoduje że program pomimo wyskakujących komunikatów o błędzie pozwala się (z trudem ale jednak) zamknąć. Ale to nie jest przecież rozwiązanie.

Problem z komponentem jest taki że po prostu nie rozpoznaje on że nie ma już portu COM, zdarzenie onError albo onException się nie zdarza chyba nigdy. To by rozwiązało problem.

Może ktoś z was już pokonał ten problemik i podzieli się uwagami albo ma jakąś sugestię?

1

Nie używałem tego komponentu, ale pierwsza myśl jaka mi się nasunęła, to łapanie komunikatu WM_DEVICECHANGE i sprawdzanie czy port istnieje. Jeśli nie istnieje to zwolnij obiekt portu, a jak się pojawi to go utwórz.

W przypadku napotkania wyjątku próbuj zwolnić obiekt (poza kodem komponentu), a jeśli ten będzie się stawiał i rzucał kolejnymi wyjątkami, próbuj go ubijać w pętli – dotąd aż przestanie pyskować. ;)

0

Tak sobie myślę - a jakby ten kod odpalić w wątku i w razie problemów po prostu ubić ten wątek? Taki pomysł mi przyszedł do głowy, ale gwarancji czy to ma sens udzielić nie mogę ;)

2

Można też olać komponenty i po prostu skorzystać z WinAPI – nie będzie żadnych wyjątków, co najwyżej liczbowe kody błędów do sprawdzania i reagowania na zaistniałe problemy. To pozwoli na implementację stabilnego rozwiązania.

0

A co za problem, mając do dyspozycji kod źródłowy, dodać sprawdzanie istnienia wybranego portu przed zapisem, czyszczeniem bufora in/out, bądź zamknięciem?

0
marogo napisał(a):

A co za problem, mając do dyspozycji kod źródłowy, dodać sprawdzanie istnienia wybranego portu przed zapisem, czyszczeniem bufora in/out, bądź zamknięciem?

oj chyba jednak jakiś problem jest skoro przez ponad 10 lat autor komponentu nie poradził sobie z tym. Według wpisów na sourceforge niestety nie przyłożono się do obsługi błędów w tym komponencie. Lata temu autor napisał że naprawił błąd w wersji 4.20 ale jej nie upublicznił (ostatnia wersja to 4.11f).
Znalazłem jednak forka tego komponentu na githubie i widać że coś się tam dzieje, przede wszystkim powstają wersje na nowe Delphi. Błąd jednak pozostał :).

Cały czas kombinuję, na teraz najbardziej skłaniam się do podpowiedzi @furious programming aby złapać komunikat błędu WinApi, chociaż inne rozwiązania w stylu zmienna wskazująca czy port istnieje także wchodzi w grę.

0

Teoretycznie @marogo ma rację – jeśli masz kod źródłowy komponentu, to masz możliwość sprawdzenia z których funkcji WinAPI kontrolka korzysta i wywalić kod odpowiedzialny za generowanie wyjątków. Wszystkie funkcje z systemowej biblioteki bazują na liczbowych kodach błędów, więc siłą rzeczy muszą być tam instrukcje rzucające wyjątki, gdy dana funkcja z WinAPI zwróci numer błędu.

W razie czego zawsze możesz sprawdzić jak wygląda kod klasy TLazSerial, która służy do tego samego. A jak chcesz coś innego to możesz też otworzyć plik o nazwie swojego portu i do niego pisać jak do normalnego pliku. Możesz to wykonać za pomocą podstawowych instrukcji, ale też za pomocą klasy TFileStream. Albo gołe WinAPI.

Sprawdzenie czy port istnieje to nic innego jak sprawdzenie czy istnieje plik o nazwie portu.

1

Nie korzystałem z tego komponentu ale jeżeli wiesz, że wyjątek powoduje procedura AbortAllAsync to tam jest jawnie wyrzucany wyjątek jeżeli funkcja PurgeComm się nie powiedzie a jak ma się powieść skoro uchwyt wskazuje na urządzenie którego już nie ma więc właśnie przed if not PurgeComm(FHandle, PURGE_TXABORT or PURGE_RXABORT) then powinieneś sprawdzać czy port istnieje czyli zwykłe sprawdzenie czy plik istnieje i jeżeli nie to nie wykonywać dalszej części kodu procedury tylko dopisać swoje własne zdarzenie w którym zareagujesz na taką sytuację zamiast wywoływać wyjątek.

3
robertz68 napisał(a):
marogo napisał(a):

A co za problem, mając do dyspozycji kod źródłowy, dodać sprawdzanie istnienia wybranego portu przed zapisem, czyszczeniem bufora in/out, bądź zamknięciem?

oj chyba jednak jakiś problem jest skoro przez ponad 10 lat autor komponentu nie poradził sobie z tym.

Z takim podejściem ,to do dziś by nie powstało koło i wiele innych rzeczy ;-)

function TCustomComPort.ComPortExists(Port: string): Boolean; //sprawdza, czy port istnieje
 Var
  Size: Cardinal;
  CommConfig:TCommConfig;
begin
  result := false;
  FillChar(CommConfig, SizeOf(TCommConfig), 0);
  CommConfig.dwSize := SizeOf(TCommConfig);
  Size := SizeOf(TCommConfig);
  if GetDefaultCommConfig(PChar(Port), CommConfig, Size) then //próba odczytu konfiguracji portu (! bez prefixu \\.\)
    result := (CommConfig.dcb.BaudRate > 0); //jeśli bitrate nie jest 0, to znaczy że port istnieje w systemie
end;

procedure TCustomComPort.Close;
begin
  //.....
  if ComPortExists(Port) then
    AbortAllAsync; 
  //.....
end;

procedure TCustomComPort.ClearBuffer(Input, Output: Boolean);
var
  Flag: DWORD;
begin
  //...
  if ComPortExists(Port) then
    if not PurgeComm(FHandle, Flag) then
  //...
end;

Przy próbie otwarcia portu, funkcja CreateFile pozwoli wykryć, czy wybrany port istnieje (albo da się otworzyć, albo nie).
Przy zapisie/odczycie nie koniecznie trzeba sprawdzać istnienie portu (przy wielu operacjach zapisu/odczytu spowolniłoby ten proces), bo załatwią to funkcje odpowiednio WriteFile/ReadFile.
Zwolnieniem zasobów przyznanych na otwarty port, który "zniknął", zajmie się metoda DestroyHandle.
Dodatkowo, jak napisał @furious programming, wskazane jest "łapanie komunikatu WM_DEVICECHANGE i sprawdzanie czy port istnieje" wykorzystując metodę ComPortExists, jeśli chciałbyś np. powiadamiać użytkownika, że pociągnął za kabel i program utracił połączenie z urządzeniem. No i wtedy na końcu zamykasz otwarty port.

3

przyznam że kilkukrotne użycie klasy Exception trochę rozwiązało problem ale jako że podrzuciliście kilka ciekawych podpowiedzi to czuję się zobowiązany to sprawdzić.
Jutro wszystko potestuję i dam znać.

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0