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

1. Zanim zaczniesz

W ćwiczeniach z programowania Router graniczny Thread (TBR) pokazujemy, jak zbudować router graniczny Thread na bazie Raspberry Pi. W tych ćwiczeniach z programowania

  • Nawiązywanie dwukierunkowego połączenia IP między sieciami Thread i Wi-Fi/Ethernet.
  • Zapewnia dwukierunkowe wykrywanie usług za pomocą mDNS (w przypadku połączenia Wi-Fi/Ethernet) i SRP (w przypadku sieci Thread).

Te ćwiczenia z programowania są kontynuacją poprzednich i pokazują, jak własny router graniczny i aplikacja mogą wchodzić w interakcje z interfejsami API Google, aby utworzyć jedną sieć Thread. Ujednolicenie danych logowania do Thread jest ważne, ponieważ zwiększa niezawodność sieci i upraszcza interakcje użytkowników z aplikacjami, które korzystają z Thread.

Wymagania wstępne

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 tymi samymi danymi logowania co sieć Google

Czego potrzebujesz

  • Płytka Raspberry Pi 4 lub inna płytka z Linuksem z uruchomionym routerem brzegowym Open Thread (OTBR)
  • Płytka zapewniająca łączność IEEE 802.15.4 jako koprocesor radiowy (RCP). Listę repozytoriów różnych dostawców układów SoC i ich instrukcje znajdziesz na stronie OpenThread na GitHubie.

2. Konfigurowanie usługi HTTP

Pierwszym elementem składowym, którego potrzebujemy, jest interfejs umożliwiający odczytywanie aktywnych danych logowania i zapisywanie oczekujących danych logowania w OTBR. Podczas tworzenia TBR używaj własnych mechanizmów zastrzeżonych, jak pokazano na tych 2 przykładach. Pierwsza opcja pokazuje, jak lokalnie połączyć się z agentem OTBR za pomocą DBUS, a druga wykorzystuje 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 aktualnej postaci. Dostawca może jednak wdrożyć szyfrowanie w przypadku obu metod, aby można było ich używać w środowisku produkcyjnym. Możesz też rozszerzyć własną usługę monitorowania, aby wysyłać wywołania HTTP zwrotne lub wywołania DBUS, które są z natury lokalne.

Opcja 1. DBUS i HTTP API w skrypcie w Pythonie

91e5fdeed83e9354.png

W tym kroku utworzysz podstawową usługę HTTP, która udostępnia 2 punkty końcowe do odczytywania i ustawiania danych logowania, a ostatecznie wywołuje polecenia DBUS.

Na RPi, który będzie służyć jako OTBR, zainstaluj zależności Pythona 3:

$ pip install dbus-python shlex json

Uruchom skrypt w ten sposób:

$  sudo python credentials_server.py 8081
serving at port 8081

Przykładowy kod konfiguruje serwer HTTP na porcie 8081 i nasłuchuje w ścieżce głównej żądania GET w celu pobrania danych logowania do sieci Thread lub żądania POST w celu ustawienia danych logowania do sieci Thread. Ładunek jest zawsze strukturą JSON z TLV.

Poniższe żądanie PUT ustawia nowe dane logowania wątku oczekującego w OTBR za pomocą ścieżki /node/dataset/pending. W tym przypadku oczekujące dane logowania zostaną zastosowane po 10 sekundach:

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 logowania.

GET /node/dataset/active
Host: <IP>:8081
ContentType = "application/json"
acceptMimeType = "text/plain"
...
<TLV encoded Thread Dataset>

Skrypt wywołuje polecenia odczytu/zapisu DBUS do ścieżki magistrali io.openthread.BorderRouter.wpan0 i ś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 introspekcję swoich możliwości. Możesz to zrobić w jeden z tych sposobów:

$ sudo dbus-send --system --dest=io.openthread.BorderRouter.wpan0 \
        --type=method_call --print-reply /io/openthread/BorderRouter/wpan0 \
        org.freedesktop.DBus.Introspectable.Introspect

Obsługiwane funkcje znajdziesz w dokumentacji tutaj.

Opcja 2. Natywny interfejs API typu REST OTBR Agent

c748ca5151b6cacb.png

Router brzegowy OpenThread jest domyślnie tworzony z flagą REST_API=1, która włącza interfejs API REST. Jeśli w poprzednich ćwiczeniach z programowania nie udało Ci się włączyć interfejsu REST API, pamiętaj, aby podczas tworzenia OTBR na RPi użyć tego flagi:

$ REST_API=1 INFRA_IF_NAME=wlan0 ./script/setup

Agenta OTBR można ponownie uruchomić, wykonując to polecenie:

$ sudo systemctl restart otbr-agent.service

Agent uruchamia serwer HTTP na porcie 8081. Ten serwer umożliwia użytkownikowi lub programowi monitorującemu wykonywanie wielu zadań w OTBR (opisanych tutaj). Aby sprawdzić jego zawartość, możesz użyć przeglądarki, curl lub wget. Wśród wielu obsługiwanych ścieżek znajdują się opisane powyżej przypadki użycia z czasownikiem GET w przypadku /node/dataset/active i czasownikiem PUT w przypadku /node/dataset/pending.

3. Konfigurowanie platformy danych logowania na Androidzie

Preferowane dane logowania

Usługi Google Play na Androidzie umożliwiają i oczekują rejestracji danych logowania wszystkich TBR w Twojej sieci. Każdy z nich jest identyfikowany przez identyfikator agenta routera brzegowego (BAID). Aby wykonać to zadanie, użyjesz metody addCredentials() interfejsu ThreadNetworkClient. Pierwszy TBR dodany do pamięci Usług Google Play określa preferowane dane logowania na tym urządzeniu mobilnym.

Aplikacja, która dodaje do swojego identyfikatora BAID zestaw danych logowania do sieci Thread, staje się właścicielem tych danych i ma pełne uprawnienia do uzyskiwania do nich dostępu. 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 dla każdej aplikacji po uzyskaniu zgody użytkownika. Zalecamy aktualizowanie danych logowania przechowywanych w Usługach Google Play, gdy sieć routera brzegowego Thread jest aktualizowana. Obecnie nie wykorzystujemy tych informacji, ale w przyszłości możemy udostępnić ulepszone ścieżki.

Nawet jeśli pierwszy TBR zostanie później wykluczony, preferowane dane logowania pozostaną na urządzeniu z Androidem. Po ustawieniu inne aplikacje, które zarządzają danymi logowania Thread, mogą je uzyskać za pomocą wywołania getPreferredCredentials().

Synchronizacja TBR Google

Urządzenia z Androidem automatycznie synchronizują się z TBR Google. Jeśli na urządzeniu z Androidem nie ma danych logowania, pobiera ono je z usługi Google TBR w Twojej sieci, a te dane logowania stają się preferowanymi danymi logowania. Synchronizacja między urządzeniami TBR a urządzeniem z Androidem następuje tylko wtedy, gdy urządzenie TBR jest sparowane z jednym użytkownikiem lub z dwoma użytkownikami, którzy znajdują się w tym samym domu inteligentnym (strukturze).

Ten proces będzie też zachodzić, gdy inny użytkownik Google korzysta z GHA na Androida lub GHA na iOS w tej samej strukturze. W przypadku GHA na iOS preferowane dane logowania są ustawiane w pamięci iOS, jeśli nie istnieją żadne preferowane dane logowania.

Jeśli w tej samej sieci znajdują się 2 urządzenia z Androidem (lub Android + iGHA) z różnymi zestawami preferowanych danych logowania, na urządzeniu TBR będzie obowiązywać konfiguracja pierwotna.

Wprowadzenie do TBR firm zewnętrznych

Obecnie miejsce przechowywania danych logowania nie jest ograniczone do domu inteligentnego użytkownika (struktury). Każde urządzenie z Androidem będzie miało swój własny magazyn BAID, ale gdy w sieci pojawi się Google TBR, inne urządzenia z Androidem i urządzenia z iOS z aplikacją Google Home na iOS będą synchronizować się z tym TBR i próbować ustawić lokalne dane logowania w pamięci telefonu.

Zanim nowy OOB TBR utworzy sieć, ważne jest sprawdzenie, czy preferowana sieć nie istnieje już w pamięci Androida.

  • Jeśli istnieje sieć preferowana, dostawca powinien z niej korzystać. 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 będzie traktować te dane logowania jako standardowe dane logowania ustawione na wszystkich routerach TBR opartych na Google, a inni dostawcy będą mogli zwiększyć zasięg i niezawodność Twojej sieci typu mesh za pomocą dodatkowych urządzeń.

cd8bc726f67b1fa1.png

4. Klonowanie i modyfikowanie aplikacji na Androida

Utworzyliśmy aplikację na Androida, która prezentuje główne możliwe wywołania interfejsu Thread API. Możesz używać tych wzorców w swojej aplikacji. W tym laboratorium kodowania sklonujemy z GitHuba przykładową aplikację Google Home dla Matter.

Cały kod źródłowy pokazany tutaj jest już zakodowany w przykładowej aplikacji. Możesz go zmodyfikować zgodnie z własnymi potrzebami, ale możesz też po prostu sklonować aplikację lub uruchomić gotowe pliki binarne, aby sprawdzić jej działanie.

  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 za pomocą kabla USB.
  5. Uruchom aplikację z Android Studio, naciskając <Cmd+R> (OS X) lub <Ctrl+R> (Windows, Linux).
  6. Kliknij kolejno Koło –> Narzędzia deweloperskie –> Sieć Thread.
  7. Wypróbuj różne dostępne opcje. W sekcjach poniżej omówimy kod wykonywany na każdym przycisku.

Czy istnieją preferowane dane logowania?

Pierwsze pytanie, jakie producent TBR powinien zadać Google, to czy na urządzeniu istnieje już preferowany zestaw danych logowania. Powinien to być punkt początkowy procesu. Poniższy kod wysyła do GPS zapytanie o istnienie danych logowania. Nie wyświetla prośby o zgodę użytkownika, ponieważ nie udostępnia żadnych danych logowania.

/**
* 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 istnieją, odczytaj dane logowania. Jedyna różnica w stosunku do poprzedniego kodu polega na tym, że po otrzymaniu intentSenderResult chcesz utworzyć i uruchomić intencję, używając wyniku otrzymanego od nadawcy.

W naszym kodzie ze względów organizacyjnych i architektonicznych używamy MutableLiveData<IntentSender?>, ponieważ oryginalny kod znajduje się w obiekcie ViewModel (ThreadViewModel.kt), a obserwatorzy intencji – we fragmencie aktywności (ThreadFragment.kt). Gdy intentSenderResult zostanie opublikowany w danych na żywo, 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 uzyskanie zgody użytkownika na udostępnianie danych logowania, a jeśli użytkownik wyrazi zgodę, treści zostaną zwrócone w jeden z tych sposobów:

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, "")
   }
 }

Poniżej znajdziesz opis publikowania danych logowania w MutableLiveData<ThreadNetworkCredentials?>.

Konfigurowanie danych logowania GPS

Niezależnie od tego, czy istnieją, czy nie, musisz zarejestrować swoje TBR w Usługach Google Play. Tylko Twoja aplikacja będzie mogła odczytać dane logowania powiązane z identyfikatorem Border Agent ID urządzenia TBR, ale jeśli Twoje urządzenie TBR zarejestruje się jako pierwsze, te dane logowania zostaną skopiowane do zestawu preferowanych danych logowania. Te informacje są dostępne dla każdej aplikacji na telefonie, o ile użytkownik na to zezwoli.

/**
* 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 usługi TBR

Ta część jest zastrzeżona dla każdego dostawcy. W tym laboratorium implementujemy ją za pomocą serwera DBUS+Python HTTP Rest Server lub natywnego serwera HTTP Rest Server 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 Product

Jak pokazano wcześniej, do pobierania danych logowania z TBR używaj czasownika GET HTTP. Zobacz przykładowy skrypt w Pythonie.

Kompilacja i import

Podczas tworzenia aplikacji na Androida musisz wprowadzić zmiany w pliku manifestu, kompilacji i importach, aby obsługiwać moduł wątków Usług Google Play. Poniższe 3 fragmenty podsumowują większość zmian.

Pamiętaj, że nasza przykładowa aplikacja jest przeznaczona głównie do wdrażania Matter. Dlatego pliki manifestu i Gradle są bardziej złożone niż dodatki niezbędne do korzystania z informacji logowania do sieci 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 odkrywania mDNS/SD, aby utworzyć listę dostępnych routerów granicznych Thread w sieci oraz ich identyfikatorów BAID.

Jest to bardzo przydatne podczas wprowadzania informacji o TBR do pamięci danych logowania GPS. Jednak jego użycie wykracza poza zakres tego laboratorium. Korzystamy z biblioteki Android Service Discovery NSDManager, a pełny kod źródłowy jest dostępny w aplikacji przykładowej w sekcji ServiceDiscovery.kt.

6. Konkluzja

Po wdrożeniu tych wywołań lub użyciu aplikacji przykładowej możesz w pełni wdrożyć OTBR na RPi. Nasza aplikacja przykładowa zawiera 8 przycisków:

91979bf065e9673d.png

Możliwa kolejność wdrożenia TBR:

  1. Zapytanie o to, czy istnieją preferencyjne dane logowania (niebieskie, 1 wiersz).
  2. W zależności od odpowiedzi
  3. Uzyskaj preferowane dane logowania GPS (niebieskie, 2 wiersz)
  4. Ustawianie danych logowania TBR w GPS (niebieskie, 3 wiersz) –> Wybierz TBR –> Utwórz losowe –> Wpisz nazwę sieci –> OK
  5. Teraz, gdy masz preferowane dane logowania, ustaw je w OTBR za pomocą polecenia Set RPi OTBR credentials, które zastosuje te dane logowania do oczekującego zestawu.

W aplikacji przykładowej domyślnie stosowane jest opóźnienie 10 sekund. 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 tym laboratorium kodowania sklonowaliśmy przykładową aplikację na Androida i przeanalizowaliśmy kilka fragmentów kodu, które korzystają z interfejsów Thread Storage API w Usługach Google Play. Użyliśmy tych interfejsów API, aby uzyskać wspólny zbiór danych, który możemy wdrożyć na urządzeniu RPi TBR, co pokazuje TBR dostawcy.

Gdy wszystkie urządzenia TBR użytkownika znajdują się w tej samej sieci, zwiększa to odporność i zasięg sieci Thread. Zapobiega to też nieprawidłowym ścieżkom użytkownika, w przypadku których aplikacje nie mogą wprowadzić urządzeń Thread, ponieważ nie mają dostępu do danych logowania.

Mamy nadzieję, że te ćwiczenia z programowania i przykładowe aplikacje pomogą Ci zaprojektować i opracować własną aplikację oraz router brzegowy Thread.

8. Odniesienia

Koprocesor RCP

DBUS