Berbagi Jaringan Thread dengan Google Thread Credentials API

1. Sebelum memulai

Di codelab Thread Border Router (TBR), kami akan menunjukkan cara mem-build Router Pembatas Thread berdasarkan Raspberry Pi. Dalam codelab tersebut, kita

  • Membuat konektivitas IP Dua Arah antara jaringan Thread dan Wi-Fi/Ethernet.
  • Menyediakan penemuan layanan Dua arah melalui mDNS (pada link Wi-Fi/Ethernet) dan SRP (di jaringan Thread).

Codelab ini dibuat berdasarkan sebelumnya, yang membahas cara router pembatas Anda sendiri dan aplikasi Anda dapat berinteraksi dengan Google API untuk membuat Jaringan Thread tunggal. Menggabungkan kredensial Thread penting karena menambah ketangguhan jaringan dan menyederhanakan interaksi pengguna dengan aplikasi yang mengandalkan Thread.

Prasyarat

  • Selesaikan Codelab OTBR
  • Pengetahuan dasar tentang jaringan Linux, Android/Kotlin, dan Thread

Yang akan Anda pelajari

  • Cara menggunakan Thread Sharing API untuk Mendapatkan dan Menetapkan kumpulan kredensial
  • Cara menyiapkan Router Pembatas OpenThread Anda sendiri dengan kredensial yang sama dengan jaringan Google

Yang akan Anda butuhkan

  • Board Raspberry Pi 4 atau board berbasis Linux lainnya yang menjalankan Open Thread Border Router (OTBR)
  • Board yang menyediakan konektivitas IEEE 802.15.4 sebagai Radio Co-Processor (RCP). Lihat daftar repositori berbagai vendor SoC dan petunjuknya di halaman GitHub OpenThread

2. Menyiapkan layanan HTTP

Elemen penyusun pertama yang kita perlukan adalah antarmuka yang memungkinkan kita membaca Kredensial Aktif dan menulis Kredensial Tertunda ke OTBR Anda. Saat membangun TBR, gunakan mekanisme eksklusif Anda sendiri, seperti yang ditunjukkan di sini dengan dua contoh. Opsi pertama menunjukkan cara berinteraksi dengan agen OTBR secara lokal melalui DBUS, sedangkan opsi kedua memanfaatkan Rest API yang dapat dibangun di OTBR.

Tidak ada metode yang aman, dan tidak boleh digunakan apa adanya di lingkungan produksi. Namun, vendor dapat membuat enkripsi pada kedua metode tersebut untuk menggunakannya di lingkungan produksi, atau Anda dapat memperluas layanan monitor Anda sendiri untuk mengeluarkan loopback HTTP atau panggilan DBUS lokal yang melekat.

Opsi 1: DBUS dan HTTP API di Python Script

91e5fdeed83e9354.pngS

Langkah ini membuat layanan HTTP sederhana yang mengekspos dua endpoint untuk membaca dan menetapkan kredensial, yang pada akhirnya memanggil perintah DBUS.

Di RPi yang akan berfungsi sebagai OTBR Anda, instal dependensi Python 3:

$ pip install dbus-python shlex json

Jalankan skrip sebagai:

$  sudo python credentials_server.py 8081
serving at port 8081

Contoh ini menyiapkan server HTTP pada port 8081 dan memproses jalur root untuk permintaan GET guna mengambil kredensial Thread, atau permintaan POST untuk menetapkan kredensial Thread. Payload selalu berupa struktur JSON dengan TLV.

Permintaan PUT berikut menetapkan Kredensial Thread Tertunda baru ke OTBR melalui jalur /node/dataset/pending. Dalam hal ini, kredensial yang tertunda akan diterapkan dalam waktu 10 detik:

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
}

Permintaan GET ke /node/dataset/active mengambil Kredensial Aktif saat ini.

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

Skrip memanggil perintah DBUS R/W ke jalur bus io.openthread.BorderRouter.wpan0, jalur objek /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 memungkinkan introspeksi kemampuannya. Anda dapat melakukannya sebagai:

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

Anda juga dapat memeriksa kemampuan yang didukung yang didokumentasikan di sini.

Opsi 2: OTBR Agent native HTTP Rest API

c748ca5151b6cacb.png

Router Pembatas OpenThread di-build secara default dengan flag REST_API=1, yang mengaktifkan REST API. Jika build dari codelab sebelumnya tidak mengaktifkan REST API, pastikan untuk mem-build OTBR di RPi Anda dengan tanda tersebut:

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

Agen OTBR dapat dimulai ulang dengan menjalankan:

$ sudo systemctl restart otbr-agent.service

Agen memulai server HTTP pada port 8081. Server ini memungkinkan program pengguna atau monitor untuk melakukan banyak tugas di OTBR (didokumentasikan di sini). Anda dapat menggunakan browser Anda, curl atau wget untuk memeriksa kontennya. Di antara banyak jalur yang didukung adalah kasus penggunaan yang dijelaskan di atas, dengan kata kerja GET pada /node/dataset/active dan kata kerja PUT pada /node/dataset/pending

3. Menyiapkan Framework Kredensial di Android

Kredensial Pilihan

Layanan Google Play di Android mengizinkan dan mengharapkan pendaftaran kredensial untuk semua TBR di jaringan Anda. Setiap ID diidentifikasi oleh Border Router Agent ID (BAID) miliknya. Anda akan menggunakan metode addCredentials() dari antarmuka ThreadNetworkClient untuk melakukan tugas ini. TBR pertama yang ditambahkan ke penyimpanan Layanan Google Play menentukan Kredensial Pilihan untuk perangkat seluler ini.

Aplikasi yang menambahkan sekumpulan kredensial jaringan Thread ke BAID-nya akan menjadi pemilik kredensial, dan memiliki izin penuh untuk mengaksesnya. Jika Anda mencoba mengakses kredensial yang ditambahkan oleh aplikasi lain, Anda akan menerima error PERMISSION_DENIED. Namun, kredensial pilihan selalu tersedia untuk aplikasi apa pun atas izin pengguna. Sebaiknya selalu perbarui kredensial yang disimpan di layanan Google Play saat jaringan Router Pembatas Thread diupdate. Meskipun saat ini informasi tersebut tidak digunakan, kami mungkin menyediakan perjalanan yang lebih lengkap di masa mendatang.

Meskipun TBR pertama kemudian dikecualikan, Kredensial Pilihan akan tetap ada di perangkat Android. Setelah ditetapkan, Aplikasi lain yang mengelola kredensial Thread mungkin mendapatkan kredensial dari panggilan getPreferredCredentials().

Sinkronisasi Google TBR

Perangkat Android disinkronkan dengan TBR Google secara otomatis. Jika tidak ada kredensial di Android, perangkat akan mengekstraknya dari TBR Google di jaringan Anda, dan kredensial tersebut menjadi Kredensial Pilihan. Sinkronisasi antara TBR dan perangkat Android hanya terjadi jika TBR disambungkan dengan satu pengguna, atau jika disambungkan dengan dua pengguna yang berada di Smart Home yang sama ( Struktur).

Proses ini juga akan terjadi jika pengguna Google lain menggunakan GHA untuk Android atau GHA untuk iOS saat pengguna berada di Struktur yang sama. Dalam kasus GHA untuk iOS, kredensial pilihan ditetapkan di penyimpanan iOS, jika tidak ada kredensial pilihan.

Jika dua perangkat Android (atau Android + iGHA) berada di jaringan yang sama dengan kumpulan kredensial pilihan yang berbeda, perangkat yang awalnya mengonfigurasi TBR akan berlaku pada TBR.

Orientasi TBR pihak ketiga

Penyimpanan kredensial saat ini tidak dicakup oleh Smart Home pengguna ( Struktur). Setiap perangkat Android akan memiliki penyimpanan BAID, tetapi setelah ada TBR Google di jaringan, perangkat Android dan perangkat iOS lainnya yang menjalankan Aplikasi Google Home untuk iOS akan disinkronkan dengan TBR tersebut dan mencoba menetapkan kredensial lokal di penyimpanan ponsel.

Sebelum OOB TBR baru membuat jaringan, penting untuk memeriksa apakah jaringan yang dipilih sudah ada di penyimpanan Android.

  • Jika jaringan yang dipilih sudah ada, vendor harus menggunakannya. Ini memastikan bahwa perangkat Thread terhubung ke jaringan Thread tunggal jika memungkinkan.
  • Jika tidak ada jaringan pilihan, buat kumpulan kredensial baru dan tetapkan ke TBR Anda di Layanan Google Play. Android akan menerima kredensial tersebut sebagai kredensial standar yang ditetapkan di semua TBR berbasis Google, dan vendor lainnya akan dapat meningkatkan jangkauan dan keandalan mesh Anda dengan perangkat tambahan

cd8bc726f67b1fa1.png

4. Meng-clone dan Mengubah Aplikasi Android

Kami telah membuat Aplikasi Android yang menampilkan kemungkinan panggilan utama ke Thread API. Anda dapat menggunakan pola ini di aplikasi Anda. Dalam codelab ini, kita akan meng-clone Aplikasi Contoh Google Home untuk Matter dari GitHub.

Semua kode sumber yang ditampilkan di sini sudah dikodekan dalam aplikasi contoh. Anda diundang untuk memodifikasinya sesuai kebutuhan, tetapi Anda cukup meng-clone aplikasi atau menjalankan biner siap pakai untuk memeriksa fungsionalitas.

  1. Clone menggunakan:
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
  1. Download dan buka Android Studio.
  2. Klik File > Open dan arahkan ke repositori yang Anda clone.
  3. Aktifkan mode developer di ponsel Android Anda.
  4. Hubungkan ke komputer melalui kabel USB.
  5. Jalankan Aplikasi dari Android Studio melalui <Cmd+R> (OS X) atau <Ctrl+R> (Win, Linux)
  6. Arahkan ke Roda -> Utilitas Developer -> Jaringan Thread
  7. Berinteraksi dengan berbagai opsi yang tersedia. Di bagian di bawah ini, kita akan mengekstrak kode yang dieksekusi pada setiap tombol.

Apakah ada kredensial yang dipilih?

Pertanyaan pertama yang harus diajukan produsen TBR kepada Google adalah apakah kumpulan kredensial yang dipilih sudah ada di perangkat. Ini harus menjadi titik awal dari alur Anda. Kode di bawah menanyakan GPS tentang keadaan kredensial. Tindakan ini tidak meminta izin pengguna karena tidak ada kredensial yang dibagikan.

/**
* 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)
 }
}

Mendapatkan kredensial pilihan GPS

Jika ada, Anda perlu membaca kredensialnya. Satu-satunya perbedaan dari kode sebelumnya adalah setelah menerima intentSenderResult, Anda ingin membangun dan meluncurkan intent menggunakan hasil dari pengirim tersebut.

Dalam kode, untuk tujuan pengaturan/arsitektur, kita menggunakan MutableLiveData<IntentSender?> karena kode asli ada di ViewModel (ThreadViewModel.kt) dan observer intent berada di Fragment Aktivitas ( ThreadFragment.kt). Dengan demikian, setelah intentSenderResult diposting ke data langsung, kami akan mengeksekusi konten pengamat ini:

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()
 }
}

Tindakan ini akan memicu izin pengguna dengan kredensial berbagi, dan, jika disetujui, akan menampilkan konten melalui:

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

Memposting kredensial ke MutableLiveData<ThreadNetworkCredentials?> dijelaskan di bawah.

Menyetel kredensial GPS

Baik ada maupun tidak, Anda harus mendaftarkan TBR Anda ke Layanan Google Play. Aplikasi Anda akan menjadi satu-satunya yang dapat membaca kredensial yang terkait dengan ID Agen Perbatasan TBR Anda, tetapi jika TBR Anda adalah yang pertama mendaftar, kredensial tersebut akan disalin ke kumpulan Kredensial Pilihan. Informasi tersebut dapat diakses oleh Aplikasi apa saja di ponsel, selama pengguna mengizinkannya.

/**
* 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)
     }
 }
}

Menetapkan kredensial ke Produk TBR Anda

Bagian ini merupakan hak milik setiap vendor, dan dalam codelab ini, kami menerapkannya melalui DBUS+Python HTTP Rest Server atau HTTP Rest Server native dari 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()
)

Mendapatkan kredensial dari Produk TBR Anda

Seperti yang ditunjukkan sebelumnya, gunakan GET HTTP Verb untuk mendapatkan kredensial dari TBR Anda. Lihat contoh skrip Python.

Build dan Impor

Saat membuat Aplikasi Android, Anda harus melakukan perubahan pada manifes, build, dan impor untuk mendukung Modul Thread Layanan Google Play. Tiga cuplikan berikut merangkum sebagian besar penambahan tersebut.

Perlu diperhatikan bahwa aplikasi contoh kami terutama dibuat untuk commissioning Matter. Oleh karena itu, file Manifes dan Gradle-nya lebih kompleks daripada penambahan yang diperlukan untuk hanya menggunakan Kredensial Thread.

Perubahan manifes

<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'

Impor yang Relevan

// 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. Penemuan mDNS/SD

Aplikasi contoh kami menggunakan penemuan mDNS/SD untuk membuat daftar Router Pembatas Thread yang tersedia di jaringan, serta BAID-nya masing-masing.

Hal ini sangat membantu saat memasukkan informasi TBR Anda ke dalam penyimpanan kredensial GPS. Namun, penggunaannya berada di luar cakupan codelab ini. Kami menggunakan library Penemuan Layanan Android NSDManager, dan kode sumber lengkapnya tersedia di Contoh Aplikasi, di ServiceDiscovery.kt.

6. Rangkuman

Setelah menerapkan panggilan ini atau menggunakan Aplikasi Contoh, Anda dapat sepenuhnya mengaktivasi RPi OTBR Anda. Aplikasi Contoh kami mengekspos 8 tombol:

91979bf065e9673d.png

Urutan yang mungkin untuk mengaktifkan TBR Anda adalah:

  1. Buat kueri apakah kredensial istimewa ada (biru, baris ke-1)
  2. Bergantung pada jawabannya
  3. Dapatkan kredensial pilihan GPS (biru, baris ke-2)
  4. Tetapkan kredensial TBR di GPS (biru, baris ke-3) -> Pilih TBR Anda -> Create Random -> Masukkan nama jaringan -> Ok
  5. Setelah Anda memiliki kredensial pilihan, setel kredensial tersebut ke OTBR menggunakan Setel kredensial RPi OTBR, yang akan menerapkan kredensial tersebut ke set yang tertunda.

Default untuk aplikasi contoh adalah menggunakan penundaan 10 detik. Jadi, setelah periode ini, kredensial RPi TBR Anda (dan node lain yang mungkin ada di jaringannya) akan dimigrasikan ke set data baru.

7. Kesimpulan

Dalam codelab ini, kita meng-clone contoh Aplikasi Android dan menganalisis beberapa cuplikan kode yang menggunakan Thread Storage API Layanan Google Play. Kami menggunakan API tersebut untuk memiliki set data umum yang dapat diaktivasi pada RPi TBR, yang menampilkan TBR vendor.

Memiliki semua TBR pengguna di jaringan yang sama akan meningkatkan ketahanan dan jangkauan Jaringan Thread. Hal ini juga mencegah perjalanan pengguna yang cacat saat aplikasi tidak dapat mengaktivasi Perangkat Thread karena tidak memiliki akses ke kredensial.

Kami harap codelab dan Aplikasi Contoh ini membantu Anda mendesain dan mengembangkan Aplikasi Anda sendiri dan produk Router Pembatas Thread Anda.

8. Referensi

Ko-pemroses RCP

DBUS