1. Zanim zaczniesz
W naszych ćwiczeniach z programowania Thread Border Router (TBR) pokazujemy, jak utworzyć router Thread Border Router oparty na Raspberry Pi. W tych ćwiczeniach z programowania
- Nawiąż dwukierunkowe połączenie IP między Thread i sieciami Wi-Fi/Ethernet.
- Zapewnia wykrywanie usług dwukierunkowych przez mDNS (w połączeniu Wi-Fi/Ethernet) i SRP (w sieci Thread).
To ćwiczenie w programie opiera się na poprzednim, lecz także o tym, jak Twój router graniczny i aplikacja mogą współdziałać z interfejsami API Google w celu utworzenia sieci typu 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 ćwiczenia z programowania OTBR
- Podstawowa wiedza o systemach Linux, Android/Kotlin i Threading
Czego się nauczysz
- Pobieranie i ustawianie zestawów danych logowania przy użyciu interfejsów Thread Share API
- Jak skonfigurować własny router OpenThread Border Router za pomocą tych samych danych logowania co sieć Google
Czego potrzebujesz
- Płytka Raspberry Pi 4 lub inna karta z systemem Linux obsługująca router Open Thread Border Router (OTBR)
- Płytka zapewniająca łączność IEEE 802.15.4 jako współprocesor radiowy (RCP). Listę repozytoriów różnych dostawców SOC wraz z instrukcjami znajdziesz na stronie OpenThread GitHub
2. Konfigurowanie usługi HTTP
Pierwszym elementem, którego potrzebujemy, jest interfejs, który umożliwia odczyt aktywnych danych logowania i zapisywanie oczekujących danych logowania w OTBR. Tworząc TBR, użyj własnych zastrzeżonych mechanizmów, które przedstawiliśmy na 2 przykładach. Pierwsza opcja pokazuje, jak nawiązać lokalne połączenie z agentem OTBR przez DBUS, a druga korzysta z interfejsu Rest API, który można utworzyć w OTBR.
Żadna z tych metod nie jest bezpieczna i nie należy jej używać w środowisku produkcyjnym. Dostawca może jednak utworzyć szyfrowanie na podstawie dowolnej z tych metod, aby używać jej w środowisku produkcyjnym, lub rozszerzyć własną usługę monitorowania, aby wysyłać HTTP typu loopback lub z założenia lokalne wywołania DBUS.
Opcja 1. Interfejs API DBUS i HTTP w skrypcie w Pythonie
Ten krok powoduje utworzenie skróconej usługi HTTP, która udostępnia 2 punkty końcowe do odczytu i ustawiania danych logowania, co ostatecznie wywoła polecenia DBUS.
Na RPi, która będzie pełnić funkcję 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 skonfigurowano serwer HTTP na porcie 8081 i nasłuchujemy w ścieżce głównej albo żądania GET, aby pobrać dane logowania do Thread, lub żądania POST, aby ustawić dane logowania Thread. Ładunek ma zawsze strukturę JSON z TLV.
Następujące żądanie PUT ustawia nowe dane uwierzytelniające wątku oczekującego na OTBR za pomocą ścieżki /node/dataset/pending
. W tym przypadku oczekujące dane logowania 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 do /node/dataset/active
pobiera obecnie aktywne dane logowania.
GET /node/dataset/active Host: <IP>:8081 ContentType = "application/json" acceptMimeType = "text/plain" ... <TLV encoded Thread Dataset>
Skrypt wywołuje polecenia DBUS R/W na ścieżkę magistrali io.openthread.BorderRouter.wpan0
, ścieżkę 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 przeanalizowanie 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
Możesz też zapoznać się z dostępnymi możliwościami opisanymi tutaj.
Opcja 2. Natywny interfejs HTTP Rest API dla agenta OTBR
Router OpenThread Border Router kompiluje domyślnie z flagą REST_API=1
, co włącza interfejs API REST. Jeśli kompilacja z poprzednich ćwiczeń z programowania nie włączała interfejsu API REST, pamiętaj, aby utworzyć OTBR na RPi z tą flagą:
$ REST_API=1 INFRA_IF_NAME=wlan0 ./script/setup
Agenta OTBR można zrestartować, uruchamiając polecenie:
$ sudo systemctl restart otbr-agent.service
Agent uruchamia serwer HTTP na porcie 8081. Umożliwia on użytkownikowi lub programowi monitorującemu wykonywanie wielu zadań w OTBR (udokumentowane tutaj). Aby sprawdzić jej zawartość, możesz użyć przeglądarki, curl
lub wget
. Spośród wielu obsługiwanych ścieżek znajdują się przypadki użycia opisane powyżej, gdzie czasownik GET
w przypadku /node/dataset/active
i czasownik PUT
w przypadku /node/dataset/pending
3. Konfigurowanie platformy danych logowania na Androidzie
Preferowane dane logowania
Usługi Google Play na urządzeniach z Androidem zezwalają na rejestrację danych logowania dla wszystkich TBR w Twojej sieci i odpowiadają temu. Każdy z nich jest identyfikowany przez identyfikator agenta routera Border Router (BAID). Do wykonania tego zadania użyjesz metody addCredentials()
w interfejsie ThreadNetworkClient
. Pierwszy plik TBR dodany do pamięci Usług Google Play określa Preferowane dane logowania dla tego urządzenia mobilnego.
Aplikacja, która dodaje zestaw danych logowania do sieci Thread do swojego identyfikatora BAID, staje się właścicielem tych danych 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. Jednak preferowane dane logowania będą zawsze dostępne dla każdej aplikacji po uzyskaniu zgody użytkownika. Zalecamy, aby dane logowania przechowywane w Usługach Google Play były aktualne podczas aktualizacji sieci Thread Border Router. Nie używamy tych informacji obecnie, ale w przyszłości możemy udostępnić te rozszerzone informacje.
Nawet jeśli pierwszy plik TBR zostanie później wykluczony, dane Preferowane zostaną zachowane na urządzeniu z Androidem. Po skonfigurowaniu tej funkcji inne aplikacje zarządzające danymi logowania Thread będą mogły uzyskiwać te dane z wywołania getPreferredCredentials()
.
Synchronizacja Google TBR
Urządzenia z Androidem automatycznie synchronizują się z konsolami Google TBR. Jeśli na urządzeniu z Androidem nie ma żadnych danych logowania, urządzenia wyodrębnią je z dzienników Google TBR w Twojej sieci i stają się one na liście Preferowanych danych logowania. Synchronizacja między TBR a urządzeniem z Androidem jest wykonywana tylko wtedy, gdy TBR jest sparowany z jednym użytkownikiem lub jest sparowany z 2 użytkownikami, którzy znajdują się w tym samym inteligentnym domu ( Struktura).
Ten proces ma miejsce również wtedy, gdy inny użytkownik Google korzysta z GHA na Androida lub GHA na iOS, gdy znajduje się w tej samej strukturze. W przypadku GHA na iOS preferowane dane uwierzytelniające są ustawiane na urządzeniu z iOS, o ile nie istnieją preferowane dane logowania.
Jeśli 2 urządzenia z Androidem (lub z Androidem + iGHA) są w tej samej sieci z różnymi zestawami preferowanych danych logowania, to w TBR będzie miało miejsce to, które pierwotnie zostało skonfigurowane.
Wprowadzenie do TBR firm zewnętrznych
Inteligentny dom ( Struktura) nie jest obecnie ograniczony do miejsca na dane logowania. Każde urządzenie z Androidem będzie miało swoją pamięć BAID, ale gdy w sieci pojawi się plik TBR Google, 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 pakiet OOB TBR utworzy sieć, sprawdź, czy w pamięci Androida istnieje już preferowana sieć.
- Jeśli istnieje preferowana sieć, dostawca powinien z niej skorzystać. Dzięki temu urządzenia Thread są w miarę możliwości połączone z jedną 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 uzna te dane uwierzytelniające jako standardowe dane logowania ustawione we wszystkich TBR opartych na Google, a inni dostawcy będą mogli zwiększyć zasięg i stabilność Twojej sieci typu mesh dzięki dodatkowym urządzeniom
4. Klonowanie i modyfikowanie aplikacji na Androida
Stworzyliśmy aplikację na Androida, która prezentuje główne możliwe wywołania interfejsu Thread API. Możesz użyć tych wzorców w swojej aplikacji. W tym ćwiczeniu w programie skopiujemy z GitHuba przykładową aplikację Google Home dla Matter.
Cały widoczny tutaj kod źródłowy jest już zakodowany w przykładowej aplikacji. Możesz ją modyfikować zgodnie z własnymi potrzebami, ale możesz po prostu skopiować aplikację lub uruchomić gotowe pliki binarne, aby sprawdzić działanie tej funkcji.
- 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 i wskaż sklonowane repozytorium.
- Włącz tryb programisty na telefonie z Androidem.
- Podłącz go do komputera kablem USB.
- Uruchom aplikację z Android Studio za pomocą kombinacji klawiszy <Cmd + R> (OS X) lub <Ctrl+R> (Win, Linux).
- Kliknij Wheel -> Developer Utilities -> Thread Network.
- Korzystaj z różnych dostępnych opcji. W kolejnych sekcjach rozpakujemy kod wykonywany na każdym przycisku.
Czy istnieją preferowane dane logowania?
Pierwsze pytanie, które producent TBR powinien zadać Google, dotyczy tego, czy preferowany zestaw danych logowania już istnieje w urządzeniu. To powinien być punkt początkowy procesu. Poniższy kod wysyła zapytanie do GPS o istnienie danych logowania. Nie wyświetla prośby o zgodę użytkownika, ponieważ 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ą, musisz je odczytać. Jedyna różnica w stosunku do poprzedniego kodu polega na tym, że po otrzymaniu intentSenderResult
chcesz utworzyć i uruchomić intencję wykorzystującą ten wynik od nadawcy.
W naszym kodzie do celów związanych z organizacją i architekturą używamy elementu MutableLiveData<IntentSender?>
, ponieważ oryginalny kod znajduje się w modelu ViewModel (ThreadViewModel.kt), a obserwatorzy intencji znajdują się we fragmencie aktywności ( ThreadFragment.kt). W związku z tym po opublikowaniu metody intencjiSenderResult do aktywnych danych uruchomimy 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 aktywowanie prośby o zgodę użytkownika wraz z informacjami o udostępnianiu danych logowania i – po zatwierdzeniu – 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 do usługi MutableLiveData<ThreadNetworkCredentials?>
zostało opisane poniżej.
Ustawianie danych logowania GPS
Niezależnie od tego, czy Twój TBR istnieje, zarejestruj go w Usługach Google Play. Twoja aplikacja będzie jedyną osobą, która może odczytać dane uwierzytelniające powiązane z identyfikatorem agenta granicznego TBR, ale jeśli Twój TBR jest zarejestrowany jako pierwszy, te dane zostaną skopiowane do zestawu Preferowane dane logowania. Z tych informacji może korzystać każda aplikacja na telefonie, o ile użytkownik ją autoryzuje.
/** * 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 tych ćwiczeń w programowaniu wdrażamy je za pomocą serwera DBUS+Python HTTP Rest Server lub natywnego serwera HTTP Rest Server firmy 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 już pokazano, użyj czasownika HTTP GET, aby uzyskać dane logowania z pliku TBR. Zobacz przykładowy skrypt Pythona.
Tworzenie i importowanie
Podczas tworzenia aplikacji na Androida musisz wprowadzić zmiany w pliku manifestu, kompilacji i importach, aby obsługiwały moduł wątku Usług Google Play. Poniższe trzy fragmenty podsumowują większość wprowadzonych zmian.
Pamiętaj, że nasza przykładowa aplikacja jest przeznaczona głównie do uruchamiania standardu Matter. W związku z tym pliki manifestu i Gradle są bardziej złożone niż te dodatkowe elementy, które są potrzebne tylko 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 używa wykrywania mDNS/SD do utworzenia listy routerów obramowań granic w wątku w sieci, a także odpowiednich identyfikatorów BAID.
Jest to bardzo przydatne podczas wprowadzania informacji TBR do pamięci danych logowania GPS. Ich wykorzystanie wykracza jednak poza zakres tego ćwiczenia w Codelabs. Korzystamy z biblioteki NSDManager usługi Android Service Discovery, 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. Przykładowa aplikacja zawiera 8 przycisków:
Oto, w jaki sposób możesz rozpocząć proces rejestracji w rejestrze 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 logowania TBR w GPS (niebieski, trzeci wiersz) -> wybierz swój TBR -> Utwórz losowo -> wpisz nazwę sieci -> OK
- Skoro masz już preferowane dane logowania, skonfiguruj je za pomocą opcji Ustaw dane logowania RPi OTBR, aby zastosować je do oczekującego zestawu.
Domyślnie w przykładowej aplikacji jest używane 10-sekundowe opóźnienie. Po tym okresie dane logowania RPi TBR (i innych węzłów, które mogą istnieć w jego sieci) zostaną przeniesione do nowego zbioru danych.
7. Podsumowanie
W ramach tego ćwiczenia w programowaniu sklonowaliśmy przykładową aplikację na Androida i przeanalizowaliśmy kilka fragmentów kodu, które wykorzystują interfejsy Thread Storage API usług Google Play. Dzięki nim mamy wspólny zbiór danych, który możemy zarejestrować w RPi TBR i prezentować TBR dostawcy.
Posiadanie wszystkich TBR użytkownika w tej samej sieci zwiększa odporność i zasięg sieci Thread. Zapobiega to również problemom z użytkownikami, którzy nie mogą rejestrować urządzeń Thread, ponieważ nie mają dostępu do danych logowania.
Mamy nadzieję, że to ćwiczenie z programowania i przykładowe aplikacje pomogą Ci zaprojektować i opracować własną aplikację oraz usługę Thread Border Router.
8. Odniesienia
Współprzetwarzający dane RCP
DBUS