Udostępnianie sieci wątków za pomocą interfejsów API danych uwierzytelniających Google Thread

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

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

91e5fdeed83e9354.png

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

c748ca5151b6cacb.png

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

cd8bc726f67b1fa1.png

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.

  1. Skopiuj go za pomocą:
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
  1. Pobierz i otwórz Android Studio.
  2. Kliknij Plik > Otwórz i wskaż sklonowane repozytorium.
  3. Włącz tryb programisty na telefonie z Androidem.
  4. Podłącz go do komputera kablem USB.
  5. Uruchom aplikację z Android Studio za pomocą kombinacji klawiszy <Cmd + R> (OS X) lub <Ctrl+R> (Win, Linux).
  6. Kliknij Wheel -> Developer Utilities -> Thread Network.
  7. 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:

91979bf065e9673d.png

Oto, w jaki sposób możesz rozpocząć proces rejestracji w rejestrze TBR:

  1. Zapytanie, czy istnieją preferowane dane logowania (niebieski, pierwszy wiersz)
  2. W zależności od odpowiedzi
  3. Uzyskaj preferowane dane logowania GPS (niebieski, 2 wiersz)
  4. Ustaw dane logowania TBR w GPS (niebieski, trzeci wiersz) -> wybierz swój TBR -> Utwórz losowo -> wpisz nazwę sieci -> OK
  5. 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