Obsługa klawiaturo-podobnych urządzeń USB z poziomu Pythona
Istnieje wiele urządzeń USB udających klawiaturę USB - czytniki kodów kreskowych, kart magnetycznych, tokentów RFID i inne. Czytnik po przyłożeniu np. tokena wypisze jego ID w aktywnym polu wskazanym przez kursor na desktopie. Może to być pole formularza aplikacji webowej, ale nie jest to rozwiązanie idealne, trzeba trzymać focus na tym polu. Alternatywne rozwiązanie to przejęcie kontroli nad urządzeniem za pomocą pyusb i obsługa przesyłanych danych tylko w naszej aplikacji.
Tak więc w tym artykule zaprezentuję jak obsługiwać takie urządzenia z poziomu Pythona. Na pywawie Mateusz porównywał pracę programisty do pracy kasjerki na kasie i ciągłe pikanie skanerem kodów kreskowych. pyusb pozwoli programiście poczuć się jak to jest siedzieć za kasą
i pikać kody ;)
Urządzenia
Do testów wykorzystałem dwa czytniki - czytnik RFID (tokeny tylko do odczytu 125kHz) oraz skaner kodów kreskowych. Takie urządzenia robione są tanio przez Chińczyków. Czasami w rozsądnych cenach pojawiają się na np. Allegro. Czytnik RFID po zeskanowaniu ID tokena wypisze go tam, gdzie mamy znak zachęty/aktywne pole. Podobnie zrobi czytnik kodów kreskowych. W sieci znaleźć można podobne urządzenia skanujące np. karty magnetyczne, czy nawet adaptery IrDA.
Na początek będziemy musieli określić VendorId oraz ProductId urządzenia. Pod Linuksem można zobaczyć to w dmesg albo w lsusb. W tym pierwszym pojawi się wpis typu:
Dzięki tym identyfikatorom będziemy mogli przejąć kontrolę nad danym urządzeniem.
Kod pyusb
Do obsługi tych urządzeń wykorzystałem pyusb w wersji 1.X. W repozytoriach różnych dystrybucji nadal może być wersja 0.4, która nie będzie zgodna z poniższym kodem.
Kod umieściłem w repozytorium pyusb-keyboard-alike. Implementacja komunikacji z urządzeniem znajduje się w keyboard_alike/reader.py.
Klasa Reader powinna być w stanie obsłużyć raczej każde urządzenie USB udające klawiaturę. Konstruktor wymaga paru argumentów, od których wszystko zależy. Identyfikatory już mamy. Do ustalenie będzie rozmiar otrzymywanych danych (data_size), oraz rozmiar jednego pakietu
danych (chunk_size). PyUSB odbiera z urządzenia długą listę liczb. W rzeczywistości to zbiór pakietów danych. Trzeba zobaczyć co ile powtarza się liczba wyraźnie większa od zera. Oto 6-elementowe pakiety:
0, 0, 31, 0, 0, 0, 0, 0, 27, 0, 0, 0
Żeby dostać surowe dane wywołaj klasę z jakimiś wartościami dla tych dwóch argumentów, oraz ustaw debug na True, żeby klasa wypisała surową listę. Przy okazji zapewne rzuci wyjątkiem gdy data_size nie pokryje się z tym co odczytała. Mając surową listę będzie można dojść jakie są poprawne wartości dla tych dwóch argumentów.
Kolejny argument konstruktora to should_reset. Jeżeli ustawimy na True to klasa będzie resetować urządzenie przy łączeniu i rozłączaniu. Pozwala to uniknąć przypadków, gdy zaraz po przejęciu urządzenia coś zostaje z niego odczytane. W przypadku testowanych przeze mnie urządzeń czytnik kodów kreskowych nie ma z tym problemów, natomiast czytnik RFID blokował się po zresetowaniu - dlatego więc resetowanie jest opcjonalne.
Cała konfiguracja, przejęcie kontroli i odczytanie danych wygląda np. tak:
from keyboard_alike import reader
reader = reader.Reader(0x08ff, 0x0009, 84, 16, should_reset=False)
reader.initialize()
print(reader.read().strip())
reader.disconnect()
Metoda initialize przejmuje kontrolę nad urządzeniem. Metoda read będzie czekać na odczytanie danych i gdy już je dostanie to zdekoduje i zwróci finalny wynik. disconnect odda systemowi kontrolę nad urządzeniem.
Jak już mamy obsługę urządzenia w Pythonie to zapewne będziemy chcieli wykorzystać je w jakiejś aplikacji, np. desktopowej z PyQt4. Przykład takiej aplikacji znajduje się w repozytorium. Do widżetu listy będzie wpisywać odczytane identyfikatory tagów RFID. Całość wykorzystuje oddzielny wątek na odczyt wartości. Gdy wątek ulegnie zakończeniu (odczytano wartość albo poleciał wyjątek) to sygnał wywoła metodę _receive_data. Jeżeli dane zostały odczytane to dodane zostaną do listy, jak nie to wypisany zostanie wyjątek. Na koniec ponownie odpalony zostanie wątek odczytu z USB by podtrzymać cykl.
Urządzenie może sypać wyjątkami. Czasami zaraz po przejęciu kontroli może rzucić wyjątkiem ReadException, gdy odczyta jakieś dane (np. wynikające z komunikacji komputera z urządzeniem tuż przed przejęciem - dlatego resetowanie pomaga). Czasami może polecieć test przy przejmowaniu kontroli niezbyt precyzyjny wyjątek Could not set configuration
- zazwyczaj ponowne wykonanie skryptu załatwi sprawę. Czasami też trzeba ponownie podłączyć urządzenie jeżeli zupełnie przestało odpowiadać.
Comment article