poniedziałek, 17 lutego 2014

Bluetooth – czyli niebieskie pogaduszki


Kod źródłowy do tej lekcji znajduje się tutaj. Rozpakuj go i zaimportuj. W katalogu z kodem źródłowym znajdą się podkatalogi oznaczone numerami. Są to po prostu kolejne wersje kodu, tworzonego w ramach przykładów. Możesz analizować kolejne kroki tworzenia aplikacji, lub od razu zabrać się za kod z katalogu o najwyższym numerku.



O korzyściach płynących z możliwości korzystania z Bluetooth nie trzeba chyba nikogo przekonywać. Dziś zajmiemy się podłączaniem urządzeń z Bluetooth i komunikacją z nimi. Tradycyjnie zaczynamy od dodania niezbędnych uprawnień do pliku manifestu:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

Pierwsze uprawnienie daje nam możliwość korzystania z Bluetooth, drugie jego włączania i wyłączania (a musimy mieć taką możliwość w razie gdyby w urządzeniu BT było wyłączone).

Napiszemy aplikację która będzie w stanie :
Rozgłosić fakt swojego istnienia, tak by inne urządzenia skanując w poszukiwaniu sprzętu z BlueTooth mogły nas zobaczyć. Jeśli urządzenia nie są ze sobą sparowane, urządzenie nie siejące informacji o swoim istnieniu nie będzie widoczne.
Wyświetlić na konsoli listę sparowanych urządzeń
Wykryć inne urządzenia pozwalające się wykryć (czyli rozgłaszające fakt swojego istnienia).
Utworzyć serwer sieciowy oparty na BlueTooth, który będzie oczekiwał na podłączenie się clienta, a gdy to nastąpi prześle mu komunikat.
Połączyć się jako client do innego urządzenia na którym będzie uruchomiony serwer.

Nasza aplikacja będzie mogła działać zarówno jako client, jak i jako serwer. Zależeć to będzie od tego, który przycisk naciśniemy. Aby móc testować aplikację najlepiej będzie byś wyposażył się w dwa fizyczne urządzenia wyposażone w BlueTooth. Przyklejam parę guzików, które będą uruchamiać wcześniej omówione funkcje. Na komponencie TextView na którym aktualne wyświetlam napis „TextView” w momencie uruchomienia aplikacji pokaże się adres mac naszego urządzenia. Na TextView na którym początkowo wyświetlać się będzie napis „Połączenie nie nawiązane”, wyświetli się później tryb działania aplikacji – jako serwer lub jako client. W polu edycyjnym będziemy wpisywać mac adres urządzenia które będzie pracowało jako serwer. Wstawiłem tutaj adres swojego telefonu, żeby później nie musieć każdorazowo go podawać.





Przejdźmy teraz do kodu głównej aktywności. Na początku w metodzie onCreate podpinam komponenty wizualne do obiektów. Nic nowego ani nadzwyczajnego:



Dalej w metodzie onCreate podpinam wywołania metod realizujących poszczególne funkcjonalności do przycisków.



Wyjaśnienia mogą wymagać jedynie linie 67-69 i 75-78. W zależności od tego czy aplikacja na danym urządzeniu będzie pracować jako serwer czy client, wywoływane są wątki realizujące zadania danej strony. Oparłem to na wątkach, ponieważ np. oczekiwanie na połączenie , czy proces łączenia są operacjami które blokowałyby wątek głównej aktywności. W ten sposób wątki pracują sobie asynchronicznie, a aplikacja nie „blokuje się”. Obiekt ba z linii 76 reprezentuje fizyczny adapter Bluetooth urządzenia. Linia 77 to pobranie mac adresu wpisanego w pole edycyjne. Będzie nam ten adres potrzebny, po przekazujemy go przez parametr konstruktora do wątku clienta. Musi on wiedzieć jaki jest adres urządzenia z którym ma się łączyć. W liniach 68 i 75 w zależności od trybu działania aplikacji, wyświetlam na stosownym polu na ekranie, jaką rolę spełnia aplikacja (czy jest serwerem czy clientem).
Przejdźmy dalej. Linie 85,86 to wyświetlenie mac adresu urządzenia na ekranie oraz konsoli. W dalszej części metody onCreate aktywności (linie 87-91) obsługuję sytuację gdyby okazało się, że BlueTooth na urządzeniu jest wyłączony. Aktywność BT weryfikuję metodą isEnabled() klasy BluetoothAdapter (linia 87). Jeśli okaże się że jest wyłączony, wyświetlam komunikat z prośbą o zgodę na włączenie BT. Ponieważ intencję z prośbą wywołuję z użyciem startActivityForResult, dodałem też metodę onActivityResult która jest automatycznie wywoływana przez system kiedy użytkownik udzieli zgody na włączenie BT (albo i nie udzieli :p). Jeśli użytkownik udzieli zgody, sam system uruchomi BT a nam pozostaje tylko zainicjalizowanie obiektu którego będziemy używać do komunikowania się z fizycznym adapterem BT i ewentualnie wyświetlenie informacji na konsoli.


Przeszkoczymy na razie definicję obiektu klasy BroadcastReceiver (linie 102-117), wrócimy do niej za chwilę, a na razie przyjrzymy się metodom na końcu klasy.


Metoda dajSieWykryc() sprawia, że nasze urządzenie jest widoczne dla innych urządzeń które skanują w poszukiwaniu „kolegów” :). Jeśli o to nie zadbamy a urządzenia nie będą sparowane, nasze nie będzie widoczne dla innych. Linie 121 i 123 są odpowiedzialne za wyświetlenie zapytania o możliwość „ujawnienia się” i rozpoczęcie rozgłaszania swojego istnienia. Linia 122 to opcjonalna konfiguracja czasu rozgłaszania. Domyślnie urządzenie rozgłaszałoby przez 2 minuty, natomiast podałem mu wartość 300 (sekund) , dzięki czemu będzie widoczny przez 5 minut.





Metoda wykryjInne() powoduje rozpoczęcie poszukiwania innych urządzeń. Trwa to ok 12 sekund. Co istotne – widoczne będą tylko te urządzenia które będą rozgłaszały swoje istnienie. Kiedy jakieś urządzenie zostanie znalezione trzeba będzie obsłużyć takie zdarzenie nie przerywając przeszukiwania. Z tego powodu rejestrujemy odbiorcę komunikatu rozgłoszeniowego informującego o znalezieniu urządzenia (linia 130). Odbiorca to obiekt klasy BroadcastReceiver którego metoda onReceive jest automatycznie wywoływana w przypadku znalezienia urządzenia z włączonym rozgłaszaniem sygnału. W ramach tej metody wyciągam obiekt reprezentujący dane urządzenie fizyczne, sprawdzam czy to urządzenie zostało wcześniej sparowane i wyświetlam o nim informacje.

Łącznie działanie tych dwóch elementów przedstawia się w ten sposób, że metoda wykryjInne() rozpoczyna poszukiwanie innych urządzeń, a gdy jakiś znajdzie to zostaje automatycznie wywołana metoda onReceive obiektu klasy BroadcastReceiver zarejestrowanego jako odbiorca takiego zdarzenia. W sumie, na konsoli zostaną wyświetlone informacje o dostępnych w okolicy urządzeniach. Po uruchomieniu na konsoli widzę znalezione urządzenie (jeśli zostaną wykryte, pojawi się ich więcej ;)) :



W ramach tej klasy mamy jeszcze metodę pokazSparowane():

Jej zadaniem jest sprawdzenie listy urządzeń które wcześniej sparowaliśmy i wyświetlenie informacji o nich na ekranie. Ta metoda nie sprawdza czy urządzenia te są dostępne , a jedynie wyświetla informacje o zarejestrowanych urządzeniach które są przechowywane w systemie:


W przypadku mojego telefonu zostały wyświetlone informacje o słuchawce na Bluetooth której używam (więc siłą rzeczy musi być sparowana), oraz tablecie który musiał zostać sparowany by odbywać po BT pogaduszki z moim telefonem na potrzeby niniejszego artykułu.

Przejdźmy teraz do elementu komunikacji. Jeśli programowałeś kiedyś sieciowe połączenia client-server w Javie, na pewno rozpoznasz wiele elementów. W zasadzie jest to zwykłe połączenie po socketach z tą różnicą, że nasza komunikacja nie będzie się odbywała po sieci, a po Bluetooth. Najpierw weźmiemy na tapetę klasę odpowiadającą za działanie aplikacji jako serwer.



W linii 15 definiuję socket przy użyciu którego będzie odbywała się komunikacja. Zagadkowe mogą się wydać linie 21 i 22. Usługa musi mieć unikalny identyfikator, dzięki któremu będzie jednoznacznie rozpoznawana ( http://en.wikipedia.org/wiki/Universally_unique_identifier ). W tych liniach naszą usługę rejestruję pod nazwą „Usługa witająca” pod identyfikatorem
550e8400-e29b-41d4-a716-446655440000
Ten akurat identyfikator jest całkiem przypadkowy, skopiowałem jakiś przykład takiego identyfikatora z internetu. Linie 27-54 to klasyczna implementacja procesu nasłuchu, różnica polega na tym, że nie sieciowego a na bluetooth. Wątek czeka na kontakt ze strony serwera, po czym otwiera strumień i wysyła tekst „Witaj kolego!”.




Po tej stronie otwieram socket dla połączenia z serwerem (podobnie jak po stronie serwerowej nie socket sieciowy a po bluetooth) i odczytuję co mi serwer przysłał.
Uruchamiam teraz program na obu urządzeniach, telefon robi za serwer, tablet za clienta.

Efekty działania serwera:




Po stronie clienta:




Gdybyś zechciał rozwijać ten program, lub na jego bazie napisać coś swojego, pamiętaj że mamy do czynienia z nieco innym niż sieciowy protokołem i usługa BT z jednej strony może się łączyć tylko z jedną inną usługą.






13 komentarzy:

  1. Jak u Clienta w "public void run() {" zrobić pętlę do ciągłego odbierania ?

    OdpowiedzUsuń
  2. Zrobiłem aplikację tak samo jak u Ciebie i niestety mi nie działa ;) raz co prawda udało mi się połączyć telefonem. Jak przesyłać informację na inne urządzenie? Próbuje się połączyć z bluetooth hc 05 i nie wiem jakie to urządzenie ma uuid a na tym urządzniu nie da się zainstalować tej aplikacji i nie mogę się z nim połączyć, urządzenie jest wykrywane ale brak połączenia. Prosił bym o radę jak się połączyć z takim urządzaniem? Do czego czego służy uuid ?

    OdpowiedzUsuń
  3. Andek Tabletowy a mógłbyś mi przesłać swój kod i powiedzieć jak łączysz się z drugiem telefonem ?

    OdpowiedzUsuń
  4. Witam
    W odniesieniu do łączenia się z innymi sparowanymi urządzeniami.

    1. Jak się podłączyć do sparowanego urządzenia jak bym w połączonych urządzeniach kliknął powiązane urządzenie i tylko się z nim łączę.
    2. Po pozytywnym połączeniu jak powyżej, chciałbym uruchomić MediaPlayer na "odtwarzanie".

    Pozdrawiam

    Grzegorz Gorczyca, Wrocław
    (sgs2"małpa"mailplus.pl)

    OdpowiedzUsuń
  5. A jak to się robi w win 8?

    OdpowiedzUsuń
  6. Witam, świetny poradnik. Czy w przyszłości nie zechciałbyś dodatkowo omówic komunikacji przez bluetooth smart (low energy)? Co prawda na stronie deweloperskiej istnieje dosyc szczegolowy opis, jednakze dla osob z niewielkim doswiadczenia - takich jak ja- jego zrozumienie jest trudne.

    Pozdrawiam

    OdpowiedzUsuń
  7. mógłbyś w tych kursach dać cały projekt? :)

    OdpowiedzUsuń
  8. Czy można się łączyć z kilkoma urządzeniami jednocześnie? W sensie jeden telefon robi za serwer, a kilka innych za klientów jednocześnie?

    OdpowiedzUsuń
  9. Witam,
    mam taki problem. Mianowicie przy próbie sparowania z głośnikiem bluetooth wyświetla mi się następujący komunikat:
    "nie można sparować z urządzeniem ze względu na błędny kod pin lub klucz".
    Wie ktoś może jak to rozwiązać?

    OdpowiedzUsuń
  10. Ten komentarz został usunięty przez autora.

    OdpowiedzUsuń
  11. Witam,
    Jestem początkujący, skorzystałem z powyższego kodu i mam pytanie. Zaprogramowałem dwa przyciski (ON/OFF) które wysyłają odpowiednio (witaj/zegnaj). za każdym razem żeby odebrać komunikat trzeba przycisnąć przycisk Klient (oprogramowany zgodnie z treścią tego poradnika). wciskając on lub off wysyłam komunikat (trzeba odbierać przyciskiem klient) i teraz pytanie dlaczego w logcat czasami widzę jak zmieniają się komunikaty (żegnaj/witaj) a innym razem wygląda jakby się wszystko zacięło? Jak ewentualnie można zrobić aby klient cały czas nasłuchiwał?


    OdpowiedzUsuń
  12. Czasami podczas importowania klasy OnClickListener (po naciśnięciu klawiszy Alt+Enter) dzieje się coś dziwnego: zamiast normalnie zaimportować wybraną klasę na początku programu (w tym przypadku są dwie do wyboru), Android Studio zamienia w danym miejscu OnClickListener na DialogInterface.OnClickListener lub na View.OnClickListener. Dopiero ręczne zaimportowanie (dopisanie na początku "import android.view.View.OnClickListener;") pomaga. Czy da się jakoś rozwiązać ten problem? Niby aplikacje nadal będą działać, ale otrzymany w ten sposób kod będzie mniej czytelny i cięższy, a jego napisanie zajmie więcej czasu.

    OdpowiedzUsuń