1. Zanim zaczniesz
W ramach naszych ćwiczeń z programowania dotyczących routera granic wątków (TBR) pokazujemy, jak zbudować router granic wątków w oparciu o Raspberry Pi. W ramach ćwiczenia w Codelabs
- Nawiąż dwukierunkowe połączenia IP między sieciami Thread i Wi-Fi/Ethernet.
- Zapewnij dwukierunkowe wykrywanie usług przez mDNS (w połączeniu Wi-Fi/Ethernet) i SRP (w sieci Thread).
To ćwiczenie w Codelabs opiera się na poprzednim ćwiczeniu i pokazuje, w jaki sposób Twój router graniczny i aplikacja mogą współdziałać z interfejsami API Google w celu utworzenia pojedynczej sieci Thread. Konwertowanie danych logowania Thread jest ważne, ponieważ zwiększa stabilność sieci i upraszcza interakcje użytkowników z aplikacjami, które korzystają z Thread.
Wymagania wstępne
- Wykonaj ćwiczenie z programowania OTBR.
- Podstawowa znajomość systemów Linux i Android/Kotlin oraz sieci Thread
Czego się nauczysz
- Jak używać interfejsów Thread Sharing API do pobierania i ustawiania zestawów danych logowania
- Jak skonfigurować własny router graniczny OpenThread z takimi samymi danymi logowania jak sieć Google
Czego potrzebujesz
- Płyta Raspberry Pi 4 lub inna płyta z systemem Linux, na której działa router Open Thread Border Router (OTBR)
- Płytka zapewniająca łączność IEEE 802.15.4 jako współprocesor radiowy (RCP). Zapoznaj się z listą repozytoriów różnych dostawców SoC i ich instrukcjami na stronie OpenThread w GitHubie.
2. Konfigurowanie usługi HTTP
Pierwszym elementem, którego potrzebujemy, jest interfejs, który pozwala nam odczytywać Aktywne dane uwierzytelniające i zapisywać oczekujące dane uwierzytelniające w OTBR. Tworząc TBR, użyj własnych zastrzeżonych mechanizmów, jak tu przedstawiliśmy w 2 przykładach. Pierwsza opcja prezentuje sposób obsługi agenta OTBR lokalnie przez DBUS, a druga – interfejs Rest API, który można utworzyć na OTBR.
Żadna z tych metod nie jest bezpieczna i nie należy jej używać w środowisku produkcyjnym w takiej postaci, w jakiej są. Dostawca może jednak utworzyć szyfrowanie przy użyciu dowolnej z tych metod i używać jej w środowisku produkcyjnym albo rozszerzyć własną usługę monitorowania tak, aby wywoływała zapętlenie HTTP lub lokalne wywołania DBUS.
Opcja 1. Interfejs API DBUS i HTTP w skrypcie Pythona
Ten krok tworzy prostą usługę HTTP, która udostępnia 2 punkty końcowe do odczytu i ustawiania danych logowania, co ostatecznie wywołuje polecenia DBUS.
W RPi, która posłuży za OTBR, zainstaluj zależności Pythona 3:
$ pip install dbus-python shlex json
Uruchom skrypt jako:
$ sudo python credentials_server.py 8081 serving at port 8081
W przykładzie konfigurowaliśmy serwer HTTP na porcie 8081 i nasłuchuje w ścieżce głównej żądania GET umożliwiającego pobranie danych logowania do wątku, albo żądania POST służącego do ustawiania danych logowania do wątku. Ładunek ma zawsze strukturę JSON z TLV.
To żądanie PUT ustawia nowe dane logowania w oczekiwanym wątku na OTBR za pomocą ścieżki /node/dataset/pending
. W takim przypadku oczekujące dane uwierzytelniające są stosowane w ciągu 10 sekund:
PUT /node/dataset/pending Host: <IP>:8081 ContentType: "application/json" acceptMimeType: "application/json" ... { "ActiveDataset": "<TLV encoded new Thread Dataset>" "PendingTimestamp": { "Seconds": <Unix timestamp in seconds>, "Ticks": 0, "Authoritative": false }, "Delay": 10000 // in milliseconds }
Żądanie GET wysłane do /node/dataset/active
pobiera obecnie aktywne dane uwierzytelniające.
GET /node/dataset/active Host: <IP>:8081 ContentType = "application/json" acceptMimeType = "text/plain" ... <TLV encoded Thread Dataset>
Skrypt wywołuje polecenia DBUS R/W do ścieżki magistrali io.openthread.BorderRouter.wpan0
, ścieżki obiektu /io/openthread/BorderRouter/wpan0
# D-BUS interface def call_dbus_method(interface, method_name, *arguments): bus = dbus.SystemBus() obj = bus.get_object('io.openthread.BorderRouter.wpan0', '/io/openthread/BorderRouter/wpan0') iface = dbus.Interface(obj, interface) method = getattr(iface, method_name) res = method(*arguments) return res def get_dbus_property(property_name): return call_dbus_method('org.freedesktop.DBus.Properties', 'Get', 'io.openthread.BorderRouter', property_name) def set_dbus_property(property_name, property_value): return call_dbus_method('org.freedesktop.DBus.Properties', 'Set', 'io.openthread.BorderRouter', property_name, property_value)
DBUS umożliwia omówienie swoich możliwości. Możesz to zrobić w ten sposób:
$ sudo dbus-send --system --dest=io.openthread.BorderRouter.wpan0 \ --type=method_call --print-reply /io/openthread/BorderRouter/wpan0 \ org.freedesktop.DBus.Introspectable.Introspect
Listę obsługiwanych funkcji znajdziesz tutaj.
Opcja 2. Natywny interfejs HTTP Rest API agenta OTBR
Router granic OpenThread kompiluje się domyślnie z flagą REST_API=1
, co powoduje włączenie interfejsu API REST. Jeśli w kompilacji z poprzedniego ćwiczenia w Codelabs nie włączono interfejsu API REST, pamiętaj, aby utworzyć OTBR w RPi z tą flagą:
$ REST_API=1 INFRA_IF_NAME=wlan0 ./script/setup
Agenta OTBR można ponownie uruchomić, uruchamiając polecenie:
$ sudo systemctl restart otbr-agent.service
Agent uruchamia serwer HTTP na porcie 8081. Ten serwer umożliwia programowi użytkownika lub monitoru wykonywanie wielu zadań w trybie OTBR (zostało to udokumentowane tutaj). Aby sprawdzić zawartość pliku, możesz użyć przeglądarki curl
lub wget
. Wśród wielu obsługiwanych ścieżek są przypadki użycia opisane powyżej, w tym z czasownikiem GET
na /node/dataset/active
i czasownikiem PUT
na /node/dataset/pending
3. Konfigurowanie platformy danych logowania na urządzeniu z Androidem
Preferowane dane logowania
Usługi Google Play na Androidzie umożliwiają i wymagają rejestracji danych logowania dla wszystkich TBR w Twojej sieci. Każda z nich identyfikowana jest przez identyfikator agenta routera Border Router (BAID). Użyjesz metody addCredentials()
interfejsu ThreadNetworkClient
. Pierwsze TBR dodane do pamięci Usług Google Play określają Preferowane dane uwierzytelniające dla tego urządzenia mobilnego.
Aplikacja, która dodaje do identyfikatora BAID zestaw danych logowania do sieci Thread, staje się właścicielem danych logowania i ma pełne uprawnienia dostępu do nich. Jeśli spróbujesz uzyskać dostęp do danych logowania dodanych przez inne aplikacje, pojawi się błąd PERMISSION_DENIED. Preferowane dane logowania są jednak zawsze dostępne w przypadku każdej aplikacji po uzyskaniu zgody użytkownika. Zalecamy dbanie o aktualność danych logowania przechowywanych w Usługach Google Play podczas aktualizowania sieci Thread Border Router. Te informacje nie są obecnie wykorzystywane, ale w przyszłości możemy udostępniać bardziej szczegółowe funkcje.
Nawet jeśli pierwszy TBR zostanie później wykluczony, preferowane dane logowania pozostaną na urządzeniu z Androidem. Po ustawieniu tych danych inne aplikacje zarządzające danymi logowania Thread mogą uzyskiwać te dane z wywołania getPreferredCredentials()
.
Synchronizacja TBR Google
Urządzenia z Androidem automatycznie synchronizują się z urządzeniami TBR Google. Jeśli na urządzeniu z Androidem nie istnieją dane logowania, urządzenia wyodrębniają je z serwerów Google TBR w Twojej sieci. Dane te stają się danymi preferowanymi. Synchronizacja między TBR a urządzeniem z Androidem odbywa się tylko wtedy, gdy TBR jest sparowany z jednym użytkownikiem lub gdy jest sparowany z dwoma użytkownikami znajdującymi się w tym samym inteligentnym domu ( struktura).
Ten proces ma miejsce też wtedy, gdy inny użytkownik Google korzysta z usługi GHA w przypadku Androida lub GHA w przypadku iOS, gdy użytkownik korzysta z tej samej struktury. W przypadku usługi GHA na iOS preferowane dane logowania są ustawiane w pamięci iOS, jeśli nie istnieją preferowane dane logowania.
Jeśli w tej samej sieci znajdują się 2 urządzenia z Androidem (lub urządzenia z Androidem + iGHA) z różnymi zestawami preferowanych danych logowania, pierwszeństwo ma urządzenie, na którym pierwotnie skonfigurowano TBR.
Wprowadzenie do TBR firmy zewnętrznej
Miejsce na dane logowania nie jest obecnie ograniczone do inteligentnego domu ( Struktura) użytkownika. Każde urządzenie z Androidem będzie mieć pamięć BAID, ale gdy w sieci znajdzie się Google TBR, inne urządzenia z Androidem i iOS z aplikacją Google Home na iOS zsynchronizują się z tym urządzeniem i spróbują ustawić lokalne dane logowania w pamięci telefonu.
Zanim nowy TBR OOB utworzy sieć, sprawdź, czy w miejscu na dane Androida istnieje już preferowana sieć.
- Jeśli istnieje preferowana sieć, dostawca powinien jej użyć. Dzięki temu urządzenia Thread w miarę możliwości będą połączone z pojedynczą siecią Thread.
- Jeśli nie ma preferowanej sieci, utwórz nowy zestaw danych logowania i przypisz go do TBR w Usługach Google Play. Android będzie uznawać te dane logowania za standardowe dane logowania ustawione we wszystkich TBR z Google, a inni dostawcy będą mogli zwiększyć zasięg i niezawodność sieci typu mesh za pomocą dodatkowych urządzeń.
4. Klonowanie i modyfikowanie aplikacji na Androida
Stworzyliśmy aplikację na Androida, która przedstawia główne możliwe wywołania interfejsu Thread API. Możesz korzystać z tych wzorców w swojej aplikacji. W ramach tego ćwiczenia w programie skopiujemy z GitHub przykładową aplikację Google Home dla spraw Matter.
Cały widoczny tutaj kod źródłowy jest już napisany w przykładowej aplikacji. Możesz dostosować aplikację do swoich potrzeb, ale możesz ją po prostu skopiować lub uruchomić gotowe pliki binarne, aby sprawdzić jej działanie.
- Skopiuj go za pomocą:
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
- Pobierz i otwórz Android Studio.
- Kliknij Plik > Otwórz sklonowane repozytorium i wskaż je.
- Na telefonie z Androidem włącz tryb programisty.
- Podłącz go do komputera kablem USB.
- Uruchom aplikację z Androida Studio za pomocą klawiszy <Cmd+R> (OS X) lub <Ctrl+R> (Windows, Linux)
- Nawiguj do koła -> Narzędzia dla programistów -> Sieć Thread
- Sprawdź różne dostępne opcje. W poniższych sekcjach omówimy kod wykonywany na każdym przycisku.
Czy istnieją preferowane dane logowania?
Pierwsze pytanie, jakie producent TBR powinien zadać Google, brzmi: czy na urządzeniu istnieje już preferowany zestaw danych logowania. Powinien to być punkt początkowy procesu. Poniższy kod wysyła zapytanie do GPS na podstawie istnienia danych logowania. Nie wyświetla prośby o zgodę użytkownika, ponieważ żadne dane logowania nie są udostępniane.
/** * Prompts whether credentials exist in storage or not. Consent from user is not necessary */ fun doGPSPreferredCredsExist(activity: FragmentActivity) { try { // Uses the ThreadNetwork interface for the preferred credentials, adding // a listener that will receive an intentSenderResult. If that is NULL, // preferred credentials don't exist. If that isn't NULL, they exist. // In this case we'll not use it. ThreadNetwork.getClient(activity).preferredCredentials.addOnSuccessListener { intentSenderResult -> intentSenderResult.intentSender?.let { intentSender -> ToastTimber.d("threadClient: preferred credentials exist", activity) // don't post the intent on `threadClientIntentSender` as we do when // we really want to know which are the credentials. That will prompt a // user consent. In this case we just want to know whether they exist } ?: ToastTimber.d( "threadClient: no preferred credentials found, or no thread module found", activity ) }.addOnFailureListener { e: Exception -> Timber.d("ERROR: [${e}]") } } catch (e: Exception) { ToastTimber.e("Error $e", activity) } }
Uzyskiwanie preferowanych danych logowania GPS
Jeśli dane logowania istnieją, możesz je odczytać. Jedyną różnicą od poprzedniego kodu jest to, że po otrzymaniu intentSenderResult
chcesz utworzyć i uruchomić intencję, korzystając z tego wyniku od nadawcy.
W naszym kodzie do celów związanych z organizacją i architekturą używamy MutableLiveData<IntentSender?>
, ponieważ oryginalny kod znajduje się w modelu ViewModel (ThreadViewModel.kt), a obserwatorzy intencji znajdują się we fragmencie aktywności ( ThreadFragment.kt). Dlatego po opublikowaniu intencji SenderResult do bieżących danych wykonamy zawartość tego obserwatora:
viewModel.threadClientIntentSender.observe(viewLifecycleOwner) { sender -> Timber.d( "threadClient: intent observe is called with [${intentSenderToString(sender)}]" ) if (sender != null) { Timber.d("threadClient: Launch GPS activity to get ThreadClient") threadClientLauncher.launch(IntentSenderRequest.Builder(sender).build()) viewModel.consumeThreadClientIntentSender() } }
Spowoduje to wywołanie zgody użytkownika za pomocą danych logowania do udostępnienia i po zatwierdzeniu spowoduje zwrócenie treści przez:
threadClientLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result -> if (result.resultCode == RESULT_OK) { val threadNetworkCredentials = ThreadNetworkCredentials.fromIntentSenderResultData(result.data!!) viewModel.threadPreferredCredentialsOperationalDataset.postValue( threadNetworkCredentials ) } else { val error = "User denied request." Timber.d(error) updateThreadInfo(null, "") } }
Publikowanie danych logowania w usłudze MutableLiveData<ThreadNetworkCredentials?>
zostało opisane poniżej.
Ustawianie danych logowania GPS
Niezależnie od tego, czy istnieją czy nie, należy zarejestrować TBR w Usługach Google Play. Twoja aplikacja będzie jedyną osobą, która będzie w stanie odczytać dane logowania powiązane z identyfikatorem agenta granicznego Twojego TBR, ale jeśli TBR zarejestruje się jako pierwsza, dane logowania zostaną skopiowane do zestawu preferowanych danych logowania. Te informacje są dostępne dla każdej aplikacji na telefonie, o ile użytkownik wyrazi na to zgodę.
/** * Last step in setting the GPS thread credentials of a TBR */ private fun associateGPSThreadCredentialsToThreadBorderRouterAgent( credentials: ThreadNetworkCredentials?, activity: FragmentActivity, threadBorderAgent: ThreadBorderAgent, ) { credentials?.let { ThreadNetwork.getClient(activity).addCredentials(threadBorderAgent, credentials) .addOnSuccessListener { ToastTimber.d("threadClient: Credentials added", activity) }.addOnFailureListener { e: Exception -> ToastTimber.e("threadClient: Error adding the new credentials: $e", activity) } } }
Ustawianie danych logowania do produktu TBR
Ta część jest zastrzeżona dla każdego dostawcy, a w ramach tego ćwiczenia z programowania wdrożymy ją przy użyciu serwera odpoczynku HTTP DBUS + Python lub natywnego serwera odpoczynku HTTP z OTBR.
/** * Creates credentials in the format used by the OTBR HTTP server. See its documentation in * https://github.com/openthread/ot-br-posix/blob/main/src/rest/openapi.yaml#L215 */ fun createJsonCredentialsObject(newCredentials: ThreadNetworkCredentials): JSONObject { val jsonTimestamp = JSONObject() jsonTimestamp.put("Seconds", System.currentTimeMillis() / 1000) jsonTimestamp.put("Ticks", 0) jsonTimestamp.put("Authoritative", false) val jsonQuery = JSONObject() jsonQuery.put( "ActiveDataset", BaseEncoding.base16().encode(newCredentials.activeOperationalDataset) ) jsonQuery.put("PendingTimestamp", jsonTimestamp) // delay of committing the pending set into active set: 10000ms jsonQuery.put("Delay", 10000) Timber.d(jsonQuery.toString()) return jsonQuery } //(...) var response = OtbrHttpClient.createJsonHttpRequest( URL("http://$ipAddress:$otbrPort$otbrDatasetPendingEndpoint"), activity, OtbrHttpClient.Verbs.PUT, jsonQuery.toString() )
Uzyskiwanie danych logowania z usługi TBR
Jak pokazano wcześniej, do uzyskiwania danych logowania z TBR użyj metody GET HTTP Verb. Zobacz przykładowy skrypt Pythona.
Kompilowanie i importowanie
Podczas tworzenia aplikacji na Androida musisz wprowadzić zmiany w pliku manifestu, kompilacji i importach, aby obsługiwać moduł wątku usług Google Play. Poniższe 3 fragmenty podsumowują większość zmian.
Pamiętaj, że nasza przykładowa aplikacja jest przeznaczona głównie do obsługi standardu Matter. Dlatego pliki manifestu i Gradle są bardziej złożone niż dodatki niezbędne do korzystania z danych logowania Thread.
Zmiany w pliku manifestu
<manifest xmlns:android="http://schemas.android.com/apk/res/android" (...) <!-- usesCleartextTraffic needed for OTBR local unencrypted communication --> <!-- Not needed for Thread Module, only used for HTTP --> <uses-feature (...) android:usesCleartextTraffic="true"> <application> (...) <!-- GPS automatically downloads scanner module when app is installed --> <!-- Not needed for Thread Module, only used for scanning QR Codes --> <meta-data android:name="com.google.mlkit.vision.DEPENDENCIES" android:value="barcode_ui"/> </application> </manifest>
Build.gradle
// Thread Network implementation 'com.google.android.gms:play-services-threadnetwork:16.0.0' // Thread QR Code Scanning implementation 'com.google.android.gms:play-services-code-scanner:16.0.0' // Thread QR Code Generation implementation 'com.journeyapps:zxing-android-embedded:4.1.0' // Needed for using BaseEncoding class implementation 'com.google.guava:guava:31.1-jre'
Odpowiednie importy
// Thread Network Module import com.google.android.gms.threadnetwork.ThreadNetworkCredentials import com.google.android.gms.threadnetwork.ThreadBorderAgent import com.google.android.gms.threadnetwork.ThreadNetwork // Conversion of credentials to/fro Base16 (hex) import com.google.common.io.BaseEncoding // HTTP import java.io.BufferedInputStream import java.io.InputStream import java.net.HttpURLConnection import java.net.URL import java.nio.charset.StandardCharsets // Co-routines for HTTP calls import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch // JSON import org.json.JSONObject // Logs import timber.log.Timber // mDNS/SD import android.net.nsd.NsdServiceInfo // QR Code reader / writer import com.google.mlkit.vision.barcode.common.Barcode import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions import com.google.mlkit.vision.codescanner.GmsBarcodeScanning import com.google.zxing.BarcodeFormat import com.google.zxing.MultiFormatWriter import com.journeyapps.barcodescanner.BarcodeEncoder
5. Wykrywanie mDNS/SD
Nasza przykładowa aplikacja korzysta z funkcji wykrywania mDNS/SD do utworzenia listy routerów granic wątków dostępnych w sieci oraz odpowiadających im identyfikatorów BAID.
Jest to bardzo pomocne przy wprowadzaniu danych TBR do magazynu danych GPS. Jednak jego użycie wykracza poza zakres tego ćwiczenia. Używamy biblioteki Android Service Discovery NSDManager, a pełny kod źródłowy jest dostępny w przykładowej aplikacji w ServiceDiscovery.kt
.
6. Konkluzja
Po zaimplementowaniu tych wywołań lub użyciu przykładowej aplikacji możesz w pełni wdrożyć RPi OTBR. Nasza przykładowa aplikacja ma 8 przycisków:
Oto możliwa sekwencja wdrażania TBR:
- Zapytanie, czy istnieją preferowane dane logowania (niebieski, pierwszy wiersz)
- W zależności od odpowiedzi
- Uzyskaj preferowane dane logowania GPS (niebieski, 2 wiersz)
- Ustaw dane uwierzytelniające TBR w GPS (niebieski, 3 wiersz) -> Wybierz TBR -> Utwórz losowy -> Wpisz nazwę sieci -> OK
- Skoro masz już preferowane dane logowania, ustaw je na OTBR za pomocą opcji Ustaw dane uwierzytelniające RPi OTBR, co spowoduje zastosowanie tych danych do oczekującego zestawu.
Domyślnym ustawieniem w przykładowej aplikacji jest 10-sekundowe opóźnienie. Dlatego po tym okresie dane uwierzytelniające RPi TBR (i innych węzłów, które mogą istnieć w jego sieci) zostaną przeniesione do nowego zbioru danych.
7. Podsumowanie
W ramach tych ćwiczeń w programie sklonowaliśmy przykładową aplikację na Androida i przeanalizowaliśmy kilka fragmentów kodu, które korzystają z Usług Google Play Interfejsy API Thread Storage. Dzięki tym interfejsom API stworzyliśmy wspólny zbiór danych, który możemy wdrożyć w RPi TBR, który przedstawia TBR dostawcy.
Uwzględnienie wszystkich zasobów TBR użytkownika w tej samej sieci zwiększa odporność i zasięg sieci Thread. Zapobiega to też błędnej ścieżce użytkowników, w przypadku której aplikacje nie mogą wdrożyć urządzeń Thread, ponieważ nie mają dostępu do danych logowania.
Mamy nadzieję, że to ćwiczenie w programowaniu i przykładowe aplikacje pomogą Ci zaprojektować i opracować własną aplikację oraz usługę Thread Border Router.
8. Pliki referencyjne
Procesor dodatkowy RCP
DBUS