56-62-Burdukiewicz2.pdf

(900 KB) Pobierz
273368584 UNPDF
Programowanie wiiremote’a
Zdalne sterowanie Linuksem za pomocą wiiremote'a
Linuksem za pomocą
wiiremote'a
Bartłomiej Burdukiewicz
Pewnie zdecydowana większość z was słyszała o rewolucyjnych kontrolerach marki Nintendo,
które zadebiutowały wraz z wydaniem konsoli Wii. Nie wiele osób zdaje sobie sprawę, że możemy
wykorzystać je do innych celów. Dzięki bibliotece libcwiid połączymy go z naszym komputerem.
jak w prosty sposób możemy napisać
oprogramowanie do zdalnego stero-
wania Linuksem wykorzystując wii-
remote’a, aby tego dokonać zapoznamy się z bibliote-
ką libcwiid dzięki której w łatwy sposób zaprogramu-
jemy nasz pilot, użyjemy także urządzenia znakowego
uinput, do stworzenia wirtualnej myszy. Będziemy do te-
go potrzebować skonigurowany interfejs bluetooth, mo-
duł uinput, bibliotekę libcwiid oraz dwie diody led (cho-
dzi tu głównie o dwa źródła podczerwieni, możemy użyć
świecznika). Omówione przeze mnie zostaną najważ-
niejsze funkcje, rozwiązania. Program będziemy budo-
wać w czterech stadiach, na koniec każdego, umieszcza-
ne będą listingi z kodem, który należy dodać (będzie to
oczywiście rozszerzona wersja, o różnego rodzaju wa-
runki sprawdzające, komunikaty, niektóre elementy z
powodzeniem można ignorować wg własnego uznania).
Wszystkie polecenia jakie będę używał w artykule są
oparte o dystrybucje Linuksa Debian w wersji Squeeze.
Należy pamiętać, że podczas instalacji pakietów wyma-
gane są prawa administratora.
Przygotowywanie interfejsu Bluetooth
Jeżeli chcemy nawiązać jakiekolwiek połączenie z na-
szym pilotem, będziemy potrzebować modułu bluetooth,
jeśli posiadamy komputer przenośny zazwyczaj jest już
wbudowany, w przeciwnym przypadku prawdopodobnie
musimy go dokupić. Cena adapterów bluetooth w dzisiej-
szych czasach jest naprawdę niewielka, można je nabyć
za niecałe 10zł, dobrym pomysłem jest uprzednie spraw-
dzenie kompatybilności urządzenia z naszym systemem,
chyba nikt nie lubi wyrzucać pieniędzy w błoto. Przejdź-
my teraz do koniguracji. Należy zainstalować usługę blu-
ez poleceniem
aptitude install bluez
zaraz po instalacji wystartuje nasza usługa, aby sprawdzić
czy poprawnie skonigurowała urządzenie bluetooth na-
leży wykonać polecenie hciconig, jeśli na liście znajdzie
się interfejs hci0 wszystko jest w porządku. Możemy do-
datkowo wytestować nasz moduł skanując sieć bluetooth
w poszukiwaniu urządzeń za pomocą polecenia hcito-
ol scan .
56
marzec 2010
Zdalne sterowanie
W artykule chciałbym zaprezentować
273368584.041.png 273368584.042.png 273368584.043.png 273368584.044.png 273368584.001.png
 
Programowanie wiiremote’a
Zdalne sterowanie Linuksem za pomocą wiiremote'a
Wiiremote
Zanim przejdziemy do programowania, parę
słów o tym niezwykłym pilocie. Wiiremote
komunikuje się z konsolą Wii poprzez inter-
fejs bluetooth, składa się z 12 przycisków,
wliczam tu także “strzałki”, jeden z nich
to klawisz specjalny o nazwie power, słu-
ży do wyłączania/wyłączania konsoli, zry-
wa także połączenie bluetooth, co umożliwi
w kontrolowany sposób zakończenie naszej
aplikacji. Kontroler wyposażony jest także
w 4 diody led, służące do informowania o
stanie baterii lub id kontrolera, silnik które-
go głównym zadaniem jest wprawianie pilo-
ta w drganie, mały głośniczek nisko tonowy,
i najważniejsze kamerę oraz akcelerometr.
Dzięki kamerze możemy pobierać współ-
rzędne punktów podczerwieni, co posłuży
nam w aplikacji. Wbudowany akcelerometr
pozwala wykryć ruchy ręki, bazując na tych
danych możemy stworzyć prosty system ge-
stów. To by było na wszystko gdyby nie port
rozszerzeń, dzięki różnego rodzaju dodat-
kom takimi jak nunchuk , classic controller
lub wiimote motion plus rozszerzamy funk-
cjonalność naszego kontrolera. Firma Nin-
tendo szykuje nawet pulsometr jako kolej-
ny dodatek.
Zaczynamy
Na początek napiszemy prosty szkielet, któ-
ry z czasem rozbudujemy, funkcjonalność
jaką będzie posiadał program w tym sta-
dium to możliwość połączenia się z kon-
trolerem oraz krótki raport o statusie po-
łączenia.
W pierwszej kolejności stwórzmy kata-
log w którym będziemy trzymać źródła. Do
edycji polecam program QtCreator domyśl-
nie służy on do tworzenia aplikacji w Qt, lecz
z powodzeniem można wykorzystywać do
kompilacji/edycji prostych oraz zaawanso-
wany programów w C/C++.
Pierwszym krokiem będzie dodanie do
sekcji include pliku nagłówkowego cwiid.h ,
aby móc korzystać z dobrodziejstw jakie
udostępnia nam biblioteka libcwiid . Następ-
nie musimy określić pule adresów MAC jakie
będą dopuszczone do połączenia, każde urzą-
dzenie bluetooth jest identyi kowane przez
unikalny adres MAC, jak wcześniej wspo-
mniałem nasz pilot także posiada interfejs
bluetooth. Jeżeli chcemy uniknąć sprawdza-
nia adresu MAC dla naszego kontrolera pro-
ponuje użyć adresu, który dopuszcza wszyst-
kie urządzenia.
daniem jest zwolnienie połączenia, warto pa-
miętać, aby przed zakończeniem programu
ją wykonać, w przeciwnym przypadku mo-
że prowadzić to do trudnych w wykryciu pro-
blemów.
W tej chwili nasz program powinien wy-
glądać tak jak w pierwszym listingu, w kodzie
umieściłem funkcje sleep() , ponieważ czas
oczekiwania na połączenie jest stosunkowo
dość krótki, unikamy w ten sposób niepowo-
dzenia spowodowanego przez zbyt późno wci-
śnięte przyciski 1 oraz 2.
Callback
Callback, czyli wywołanie zwrotne, o któ-
rym wcześniej wspominałem, będzie nam
potrzebne do odbierania wiadomości z nasze-
go kontrolera. Aby wykorzystać ten mecha-
nizm musimy stworzyć specyi czną funkcje
na wzór typu cwiid_mesg_callback_t , któ-
ra powinna wyglądać, jak niżej
void wiimote_callback(cwiid_wiimote_t
*wiimote, int mesg_count, union
cwiid_mesg mesg_array[], struct
timespec *timestamp)
{
}
Libcwiid
Libcwiid jest to biblioteka napisana w C,
umożliwia ona nawiązanie połączenia z
pilotem, kontrole działania, pobieranie
informacji/statusów, obsługuje większość
rozszerzeń, które są dostępne dla naszego
kontrolera. Dzisiejszy program będzie opar-
ty prawie w całości o tą bibliotekę, wiec na-
leżało by ją zainstalować w naszym syste-
mie, będziemy potrzebować także źródeł bi-
blioteki, aby wszystko zainstalować wystar-
czy wykonać
bdaddr_t bdaddr;
bacmp(&bdaddr, BDADDR_ANY);
Należy teraz stworzyć pętle for, która zajmie
się analizowaniem wszystkich wiadomości,
na podstawie zmiennej mesg_count określi-
my jak długo pętla ma się wykonywać. Ana-
lizowana będzie tablica, która posiada infor-
macje pobrane z wiiremota.
W pierwszej kolejności definiujemy typ
bdaddr_t , który będzie przechowywał
nasz MAC, następnie za pomocą bacmp()
przypisujemy adres BDADDR_ANY , aby ze-
zwolić na połączenie wszystkim urządze-
niom bez względu na to jaki adres MAC
posiadają.
for (int i = 0; i < mesg_count; ++i)
switch (mesg_array[i].type)
cwiid_wiimote_t *wiimote = cwiid_
connect(&bdaddr, CWIID_FLAG_MESG_
IFC);
Musimy przeanalizować składnik tablicy o
nazwie type, dzięki niemu określimy jakie-
go rodzaju wiadomości odbieramy, czy są to
stany klawiszy, informacje o statusie/baterii
itp.
aptitude install libcwiid1-dev
libcwiid1
Przejdźmy teraz do funkcji głównej, od któ-
rej zależy sukces naszego połączenia. Funk-
cja zwraca nam wskaźnik do struktury
cwiid_wiimote_t , jest to deskryptor nasze-
go urządzenia, jeśli wskaźnik przyjmie war-
tość różną od zera, połączenie zostało nawią-
zane. W pierwszym argumencie funkcji na-
leży przekazać wskaźnik do bdaddr, l aga
CWIID_FLAG_MESG_IFC udostępni nam moż-
liwość obsługi wywołania zwrotnego, o któ-
rym szerzej w następnym nagłówku.
Jeśli w naszej dystrybucji nie znajdziemy
paczki dla libcwiid, należy pobrać samodziel-
nie źródła ze strony domowej cwiid'a, skom-
pilować i zainstalować.
Nasz program zajmie się jedynie ana-
lizą trzech rodzajów wiadomości – o błę-
dach, stanach klawiszy oraz punktach pod-
czerwieni.
Uwaga
Należy pamiętać aby podczas kompi-
lacji naszego programu zlinkować go
z biblioteką libcwiid, używając parame-
tru -lcwiid .
cwiid_disconnect(wiimote);
Została nam jeszcze do omówienia bardzo
prosta funkcja cwiid_disconnect() , jej za-
Rysunek 1. Wiiremote oraz podstawowe akcesoria
www.lpmagazine.org
57
273368584.002.png 273368584.003.png 273368584.004.png 273368584.005.png 273368584.006.png 273368584.007.png 273368584.008.png 273368584.009.png 273368584.010.png 273368584.011.png 273368584.012.png 273368584.013.png 273368584.014.png 273368584.015.png 273368584.016.png
Programowanie wiiremote’a
Zdalne sterowanie Linuksem za pomocą wiiremote'a
case CWIID_MESG_ERROR:
disconnected = true;
break;
case CWIID_MESG_IR: break;
case CWIID_MESG_BTN: break;
stanie przerwane, dzieje się tak gdy odejdzie-
my za daleko z naszym pilotem (zasięg blu-
etooth to około 10m~, ale w praktyce bywa z
tym różnie, liczy się tu głównie jakość nasze-
go modułu) lub w razie przytrzymania przyci-
sku power. Zdeiniujmy zmienną disconnect ,
która będzie posiadała status naszego połącze-
nia, użyjemy do tego typu logicznego, ponie-
waż istnieją tylko dwa stany (urządzenie roz-
łączone lub połączone). Dodajmy wiec linię
kodu, która zmieni wartość logiczną naszej
zmiennej disconnected – na prawdę w razie
wystąpienia błędu.
Nasza funkcja callback'a jest już gotowa,
ale wymaga inicjacji, należy powiązać nasze
urządzenie (deskryptor) z funkcją wywołania
zwrotnego.
W tym stadium programu wykorzystamy tyl-
ko CWIID_MESG_ERROR , wiadomość jest gene-
rowana, kiedy połączenie z wiiremotem zo-
Listing 1. Szkielet aplikacji.
cwiid_set_rpt_mode(wiimote, CWIID_
RPT_STATUS | CWIID_RPT_BTN | CWIID_
RPT_IR);
cwiid_set_mesg_callback(wiimote,
&wiimote_callback);
#include <iostream>
#include <cwiid.h>
using namespace std ;
Zanim powiążemy nasz callback z urządze-
niem, warto byłoby ustalić jakie informacje
mają być “wysyłane”, służy do tego funkcja
cwiid_set_rpt_mode. Wytłumaczę teraz kolej-
no działania lag:
int main () {
bdaddr_t bdaddr ;
bacmp (& bdaddr , BDADDR_ANY );
cout << "Nacisnij 1+2 aby nawiazac polaczenie \n " ;
cout . lush ();
CWIID_RPT_STATUS – dopuszcza do wy-
wołania zwrotnego, informacje o statusie
urządzenia
CWIID_RPT_BTN – informacje o statusie
przycisków
CWIID_RPT_IR – informacje o punktach
podczerwieni
sleep ( 3 );
cwiid_wiimote_t * wiimote = cwiid_connect (& bdaddr , CWIID_FLAG_MESG_IFC
| CWIID_FLAG_REPEAT_BTN );
if ( wiimote ) {
cout << "Polaczono \n " ;
cout . lush ();
} else {
cout << "Nie mozna ustanowic polaczenia \n " ;
cout . lush ();
return 0 ;
}
cwiid_disconnect ( wiimote );
return 0 ;
}
Pora na cwiid_set_mesg_callback() , w
drugim argumencie należy umieścić wskaźnik
do naszej funkcji wywołania zwrotnego. Nasz
callback jest gotowy, ale aplikacja nadal koń-
czy natychmiast działanie po ustanowieniu po-
łączenia, stworzymy zatem pętle która zablo-
kuje zakończenie programu.
do usleep(1000); while
(!disconnected);
Jako warunek zakończenia pętli użyjemy na-
szej zmiennej disconnected , gdy przyjmie
wartość prawdy, program zakończy działanie.
Efekt jaki uzyskaliśmy w tej chwili będzie wy-
glądał następująco, kiedy odejdziemy zbyt da-
leko program zakończy działanie lub jeśli wci-
śniemy przycisk power na naszym kontrole-
rze. W listingu nr 2 widzimy kod, który należy
dodać do naszego programu.
Rysunek 2. QtCreator
Uinput
Wraz z powstaniem lini 2.6.x Linuksa do
głównego kodu jądra został wprowadzony
nowy sterownik o nazwie uinput. Udostępnia
dość prosty system komunikacji z jądrem sys-
temu dzięki czemu możemy tworzyć wirtual-
ne urządzenia wyjścia oraz zarządzać nimi bez
problemu. W większości dystrybucji Linuksa
sterownik jest dodawany w formie modułu,
58
marzec 2010
273368584.017.png 273368584.018.png 273368584.019.png 273368584.020.png 273368584.021.png 273368584.022.png
 
Programowanie wiiremote’a
Zdalne sterowanie Linuksem za pomocą wiiremote'a
aby go załadować do pamięci należy wykonać
polecenie (z prawami administratora)
je na wykonywaniu operacji bez blokowa-
nia wykonania (asynchronicznie) należy wy-
czyścić strukturę uinput z różnych pamię-
ciowych śmieci, które mogą się tam zna-
leźć, memset() będzie idealna funkcją do te-
go zadania
strncpy(uinput.name, "wiimote", 7);
uinput.id.vendor = 1;
uinput.id.product = 1;
uinput.id.version = 1;
uinput.id.bustype = BUS_USB;
modprobe uinput
Dobrym pomysłem byłoby dodanie go do listy
modułów automatycznie ładowanych podczas
startu systemu, aby unikać manualnego łado-
wania za każdym razem, gdy system zostanie
uruchomiony ponownie. Gdyby okazało się że
nie ma go w naszym systemie należy go do-
kompilować ręcznie najlepiej w formie modu-
łu używając źródeł naszego jądra, możemy go
znaleźć w sekcji
memset(&uinput, 0, sizeof(uinput));
za pomocą strncpy() przypiszemy nazwę
dla urządzenia wyjścia, kolejno id.vendor ,
id.product , id.versation nie są zbyt
ważne, ale należy je wypełnić najlepiej je-
dynkami, dla bustype przypiszmy BUS_USB
można z powodzeniem użyć innych stałych
BUS_* ale nie będzie miało to żadnego zna-
czenia, przynajmniej jeśli chodzi o funk-
cjonowanie urządzenia wyjścia. Należy te-
raz zdei niować/przekazać do jądra syste-
w pierwszym parametrze przekazujemy
wskaźnik do naszej struktury, w drugim nale-
ży podać wartość znaku, którym zapełnimy ją,
ostatni parametr to wielkość pamięci która zo-
stanie nadpisana.
Gdy mamy już czystą strukturę uinput
możemy przypisać jej własności
Device Drivers ---> Input device
support ---> Miscellaneous devices --
-> User level driver support
Listing 2. Callback
Ok, rozbudujmy więc nasz program dodając
obsługę uinput, w pierwszej kolejności nale-
ży dodać nowe pliki nagłówkowe, które są
widoczne w listingu nr 3, następnie dodajmy
dwie zmienne
bool disconnected = false ;
void wiimote_callback ( cwiid_wiimote_t * wiimote , int mesg_count , union
cwiid_mesg mesg_array [] , struct timespec * timestamp )
{
for ( int i = 0 ; i < mesg_count ; ++ i ) switch ( mesg_array [ i ] . type )
{
case CWIID_MESG_ERROR :
cout << "Rozlaczono \n " ;
cout . l ush ();
disconnected = true ;
break ;
case CWIID_MESG_IR :
break ;
int uinput_fd;
struct uinput_user_dev uinput;
wstawmy je zaraz pod using namespace
std, tak aby były widoczne w całym progra-
mie, uinput_fd będzie przechowywał uchwyt
otwartego pliku, natomiast struktura uinput
podstawowe informacje dei niujące nasze no-
we urządzenie wyjścia.
uinput_fd = open("/dev/input/uinput",
O_WRONLY | O_NDELAY);
case CWIID_MESG_BTN :
break ;
}
}
Aby otworzyć plik użyjemy funkcji open() i
tu uwaga w moim przypadku ścieżka do pliku
uinput to /dev/input/uinput , ale może się
zdarzyć tak że będzie inna, należy sprawdzić
jeszcze /dev/uinput oraz /dev/misc/uinput.
Otwieramy plik do odczytu oraz zapisu więc
użyjemy l agi O_WRONLY , O_NDELAY wskazu-
int main ( int argc , char * argv []) {
Problemy z utworzeniem
urządzenia
cwiid_set_rpt_mode ( wiimote , CWIID_RPT_STATUS | CWIID_RPT_BTN |
CWIID_RPT_IR );
if ( cwiid_set_mesg_callback ( wiimote , & wiimote_callback )) {
cout << "Nie mozna utworzyc wywolania zwrotnego \n " ;
cout . l ush ();
return 0 ;
}
Jeśli mamy problemy z utworzeniem urzą-
dzenia prawdopodobnie jest to wina złych
uprawnień. Aby sprawdzić czy tak na-
prawdę jest wystarczy uruchomić nasz
program z prawami administratora, jeże-
li działa poprawnie należy zmienić upraw-
nienia dla pliku uinput za pomocą polece-
nia chmod .
do usleep ( 1000 ); while (! disconnected );
...
}
www.lpmagazine.org
59
273368584.023.png 273368584.024.png 273368584.025.png 273368584.026.png 273368584.027.png 273368584.028.png 273368584.029.png 273368584.030.png 273368584.031.png 273368584.032.png 273368584.033.png 273368584.034.png 273368584.035.png
Programowanie wiiremote’a
Zdalne sterowanie Linuksem za pomocą wiiremote'a
Listing 3. Obsługa uinput
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/input.h>
#include <linux/uinput.h>
using namespace std ;
int uinput_fd ;
struct uinput_user_dev uinput ;
void uinput_send_event ( int input_fd , __u16 type , __u16 code , __s32
value ) {
struct input_event event ;
memset (& event , 0 , sizeof ( event ));
event . type = type ;
event . code = code ;
event . value = value ;
write ( uinput_fd , & event , sizeof ( event ));
}
...
int main ( int argc , char * argv []) {
uinput_fd = open ( "/dev/input/uinput" , O_WRONLY | O_NDELAY );
if (! uinput_fd ) {
cout << "Nie mozna otworzyc urzadzenia uinput" ;
cout . lush ();
return 0 ;
}
memset (& uinput , 0 , sizeof ( uinput ));
strncpy ( uinput . name , "wiimote" , 7 );
uinput . id . vendor = 1 ;
uinput . id . product = 1 ;
uinput . id . version = 1 ;
uinput . id . bustype = BUS_USB ;
ioctl ( uinput_fd , UI_SET_EVBIT , EV_KEY );
ioctl ( uinput_fd , UI_SET_EVBIT , EV_ABS );
ioctl ( uinput_fd , UI_SET_ABSBIT , ABS_X );
ioctl ( uinput_fd , UI_SET_ABSBIT , ABS_Y );
ioctl ( uinput_fd , UI_SET_KEYBIT , BTN_LEFT );
ioctl ( uinput_fd , UI_SET_KEYBIT , BTN_RIGHT );
uinput . absmax [ ABS_X ] = 512 ;
uinput . absmin [ ABS_X ] = - 512 ;
uinput . abslat [ ABS_X ] = 0 ;
uinput . absfuzz [ ABS_X ] = 0 ;
uinput . absmax [ ABS_Y ] = 384 ;
uinput . absmin [ ABS_Y ] = - 384 ;
uinput . abslat [ ABS_Y ] = 0 ;
uinput . absfuzz [ ABS_Y ] = 0 ;
write ( uinput_fd , & uinput , sizeof ( uinput ));
if ( ioctl ( uinput_fd , UI_DEV_CREATE )) {
close ( uinput_fd );
cout << "Nie mozna stworzyc urzadzenia" ;
cout . lush ();
return 0 ;
}
...
mu informacje z jakich komponentów zbu-
dujemy nasze urządzenie wyjścia. Dla na-
szej myszy będziemy potrzebować deini-
cji obsługi klawiszy (lewy, prawy myszy)
oraz osie X, Y.
ioctl(uinput_fd, UI_SET_EVBIT,
EV_KEY);
ioctl(uinput_fd, UI_SET_EVBIT,
EV_ABS);
Powyższe funkcje należy wykonać, aby
w jakiekolwiek sposób móc deiniować
osie/klawisze. (inaczej bez nich deinicje dla
UI_SET_ABSBIT, UI_SET_KEYBIT nie mają
żadnego sensu)
ioctl(uinput_fd, UI_SET_ABSBIT,
ABS_X);
ioctl(uinput_fd, UI_SET_ABSBIT,
ABS_Y);
Zdeiniujmy teraz nasze osie X oraz Y, wy-
korzystamy do tego lag ABS_X oraz ABS_Y ,
należy zwrócić uwagę, że nie korzystamy
już z UI_SET_EVBIT , tylko z UI_SET_ABS-
BIT .
ioctl(uinput_fd, UI_SET_KEYBIT,
BTN_LEFT);
ioctl(uinput_fd, UI_SET_KEYBIT,
BTN_RIGHT);
Zostały teraz tylko klawisze myszy należy
zdeiniować BTN_LEFT oraz BTN_RIGHT .
Nie należy zapomnieć o określeniu prze-
działów dla naszych osi
uinput.absmax[ABS_X] = 512;
uinput.absmin[ABS_X] = -512;
uinput.abslat[ABS_X] = 0;
uinput.absfuzz[ABS_X] = 0;
uinput.absmax[ABS_Y] = 384;
uinput.absmin[ABS_Y] = -384;
uinput.abslat[ABS_Y] = 0;
uinput.absfuzz[ABS_Y] = 0;
Rysunek 3. Świecznik, wiiremote
60
marzec 2010
273368584.036.png 273368584.037.png 273368584.038.png 273368584.039.png 273368584.040.png
 
Zgłoś jeśli naruszono regulamin