W sklepie z tajwańsczyzną zakupiłem sobie jakiś czas temu mały gadżet aby trochę się nim pobawić a jednocześnie coś pokodować. Gadżet ten to niewielki panel LED, który można sobie oprogramować prawie do woli. Co prawda jest do tego dołączona aplikacja za pomocą, której można ją obsłużyć, ale po pierwsze wygląda tak:

Wygląd aplikacji dołączonej do panelu

a po drugie, kto by tam ufał tajwanom. Napiszemy sobie sami :).

Uwaga: Będzie trochę asemblera oraz trochę Pascala. Jak ktoś nie lubi może przejść kawałek niżej, gdzie będzie kod C#, choć wiedza poniżej jest trochę istotna i wynika z niej kod C#, który będzie na dole.

Urządzenie

Samo urządzenie podłączane jest za pomocą USB, ale w systemie widziane jest jako COM więc nie będziemy musieli się zajmować jak wykryć urządzenia HID w systemie (być może zajmiemy się tym w przyszłości). Aby móc cokolwiek napisać dla naszego panelu musimy przede wszystkim poznać jej protokół. Aby to zrobić posłużymy się disassemblerem IDA (samej obsługi dekompilatora raczej będę unikał – jeśli ktoś potrzebuje odsyłam do świetnej książki – The IDA Pro Book), a konkretniej jej darmową wersją 5.0.

Analiza

Po uruchomieniu IDA i wyświetleniu importowanych funkcji widzimy, że aplikacja korzysta z biblioteki
PCOMM i importuje kilka funkcji zaczynających się na sio_* – sugeruje to tak jak już wcześniej było wspomniane użycie COMów do komunikacji.

Wyszukajmy sobie wszelkie odwołania do metody sio_open.

Na szczęście jest ich tylko 4 więc nie będziemy mieli problemów z analizą tych miejsc. Przeglądając pobieżnie _TForm1_suiButton5Click wygląda na prostszą metodę więc od niej zaczniemy.

Kod już na początku tej metody rozdziela się na dwie części a potem spotyka się w jednym punkcie. Fragment, który rozdziela jest następujący:

Pozwoliłem sobie już dodać etykietę co robi ten kod, bo jak za chwilę zobaczymy fragment to właśnie się dzieje.

We fragmencie po prawej widzimy pętlę gdzie powtarzamy ciąg operacji: otwieramy port (sio_open) przekazując w esi numer portu do otwarcia, następnie ustawiamy pewne parametry portu (jakie? – o tym za chwilę) za pomocą sio_ioctl i ustawiamy timeout’y (sio_SetReadTimeouts) dla połączenia. Dalej zapisujemy do portu znak (‘T’ – 54h) i próbujemy go odczytać – jak się udało – mamy nasz port. Jeśli nie próbujemy kolejny port. O ile zrozumienie co jest przekazywane do metody sio_open było dość oczywiste to pozostałe metody mogą sprawić już trochę trudności. Skąd też więc możemy się dowiedzieć co oznaczają wartości przekazane do sio_ioctl? Oczywiście z dokumentacji :D. Pytanie tylko gdzie takową znaleźć?? Z pomocą przychodzą nieprzepastne skarbnice Internatu i stronie hackchina znajdujemy dokumentacji jakiegoś modułu w Pascalu (jeszcze do tego Turbo :]), który metody sio_. Dzięki temu będziemy mogli zrozumieć co się dzieje. Bierzemy pierwszą lepszą metodę, która korzysta z sio_ioctl i paczymy* :].

FLastError:=sio_ioctl(FintPort,Ord(FBaudRate),GetMode);

Widzimy zatem, że pierwszym argumentem jest port (będzie on na stosie rzucany jako ostatni), drugim prędkość a trzecim dodatkowe ustawienia odnośnie przesyłanych danych. Każdy kto na studiach programował coś z COMem od razu powinien przypomnieć sobie jakieś bity danych parzystość i inne :] Zobaczmy jak to wygląda w naszym przypadku. Port pominiemy – jako BaudRate wrzucamy 0Eh czyli 14, ale to raczej nie jest prędkość. 14 b/s nie powala nawet jak na wolny COM :). W Pascal’owej wersji widzimy że tak na prawdę wrzucamy tylko indeks prędkości (Ord) musimy zatem zobaczyć jakie są dostępne.

TBaudRate = (br50,br75,br110, br134, br150, br300, br600, br1200, br1800, br2400, br4800, br7200, br9600, br19200, br38400, br57600, br115200, br230400, br460800, br921600 );

Kilka prędkości dostępnych mamy – na 14 pozycji jest br38400 tak więc z taką prędkością będziemy wysyłać i odbierać dane. Co do wartości ostatniego parametru – 3 to jego wartość określa tryb pracy. Jest to pole bitowe i musimy poznać jego strukturę. Ponownie wszelką wiedzę daje nam dokumentacja:

function TPort.GetMode: Longint;
var
byteDB,byteSB,bytePR:Byte;
begin
byteDB:=0;
byteSB:=0;
bytePR:=0;
case FDataBits of
dbFive:byteDB:=0;
dbSix:byteDB:=1;
dbSeven:byteDB:=2;
dbEight:byteDB:=3;
end;
case FStopBits of
sbOneStopBit:byteSB:=0;
sbTwoStopBits:byteSB:=4;
end;
case FParity of
prNone:bytePR:=0;
prOdd:bytePR:=8;
prEven:bytePR:=$18;
prMark:bytePR:=$28;
prSpace:bytePR:=$38;
end;
Result:=byteDB or byteSB or bytePR;
end;

Widzimy, że odpowiednia wartość budowana jest z 3 wartości: ilości bitów danych (bity 1-0), bitu stopu (bit 2) oraz bitów parzystości (bity 5-3). W naszym przypadku wartość 3 oznacza odpowiednio: 8 bitów danych, 1 bit stopu oraz brak parzystości. Uff. Mając tyle wiedzy chyba możemy przystąpić do napisania kawałku kodu w C#.

.NET

Na szczęście w C# mamy dostępną klasę SerialPort (System.IO.Ports), za pomocą której możemy obsłużyć porty COM.

Code Snippet
  1. for (int i = 1; i <= 8; i++)
  2. {
  3.     var serialPort = new SerialPort(“COM” + i, 38400, Parity.None, 8, StopBits.One) {ReadTimeout = 10};
  4.     try
  5.     {
  6.         serialPort.Open();
  7.     }
  8.     catch (IOException)
  9.     {
  10.         continue;                    
  11.     }
  12.     try
  13.     {
  14.         serialPort.Write(“T”);
  15.         int read = serialPort.ReadChar();
  16.         if ((char)read == ‘T’)
  17.             return serialPort.PortName;
  18.     }
  19.     catch (Exception)
  20.     {                    
  21.         continue;
  22.     }
  23. }
  24. return “None”;

Kod jest niezwykle prosty więc tylko dla porządku. W pętli sprawdzamy COM1 – COM8 poprzez utworzenie obiektu i ustawienie takich samych parametrów jak wzorcowa aplikacja. Ustawiamy też timeout czytania na 10 ms (to samo robi wzorcowa aplikacja, ale nie zostało to pokazane na fragmencie powyżej). Następnie próbujemy otworzyć port a w przypadku wystąpienia wyjątku sprawdzamy kolejny. W przeciwnym razie zapisujemy do portu literę ‘T’ (czemu akurat ‘T’ wysyła apka? – nie wiem) a następnie próbujemy ją odczytać. Jeśli się uda i uzyskamy tę samą literę mamy nasz port. Voila!

Na razie kod nie robi zbyt wiele, ale wszelkie dostępne materiały będę publikował na GitHub’ie – LEDBoard.
Następnym razem analiza drugiej z metod. Spróbujemy zanalizować i napisać program potrafiący wysłać coś do naszego wyświetlacza.