Berbagi Jaringan Thread dengan Google Thread Credentials API

1. Sebelum memulai

Dalam codelab Thread Border Router (TBR), kami menunjukkan cara membuat Thread Border Router berdasarkan Raspberry Pi. Dalam codelab tersebut, kita

  • Menetapkan konektivitas IP Bidireksional antara jaringan Thread dan Wi-Fi/Ethernet.
  • Menyediakan penemuan layanan Bidirectional melalui mDNS (di link Wi-Fi/Ethernet) dan SRP (di jaringan Thread).

Codelab ini dibuat berdasarkan codelab sebelumnya, yang membahas cara router batas Anda sendiri dan aplikasi Anda dapat berinteraksi dengan Google API untuk membuat satu Jaringan Thread. Menggabungkan kredensial Thread penting karena meningkatkan keandalan jaringan dan menyederhanakan interaksi pengguna dengan aplikasi yang mengandalkan Thread.

Prasyarat

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

Yang akan Anda pelajari

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

Yang Anda butuhkan

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

2. Menyiapkan layanan HTTP

Blok bangunan pertama yang kita butuhkan adalah antarmuka yang memungkinkan kita membaca Kredensial Aktif dan menulis Kredensial Tertunda ke OTBR Anda. Saat membuat 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.

Kedua metode tersebut tidak aman, dan tidak boleh digunakan apa adanya di lingkungan produksi. Namun, vendor dapat membuat enkripsi di sekitar salah satu metode untuk menggunakannya di lingkungan produksi, atau Anda dapat memperluas layanan monitor Anda sendiri untuk mengeluarkan panggilan HTTP loopback atau DBUS lokal secara inheren.

Opsi 1: DBUS dan HTTP API di Skrip Python

91e5fdeed83e9354.png

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

Di RPi yang akan berfungsi sebagai OTBR, 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 di port 8081 dan memproses jalur root untuk permintaan GET guna mengambil kredensial Thread, atau permintaan POST guna menetapkan kredensial Thread. Payload selalu berupa struktur JSON dengan TLV.

Permintaan PUT berikut menetapkan Kredensial Thread Tertunda baru ke OTBR menggunakan jalur /node/dataset/pending. Dalam hal ini, kredensial tertunda diterapkan dalam 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 dibuat secara default dengan tanda REST_API=1, yang mengaktifkan REST API. Jika build Anda dari codelab sebelumnya tidak mengaktifkan REST API, pastikan untuk membuat OTBR di RPi 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 di port 8081. Server ini memungkinkan program pengguna atau monitor melakukan banyak tugas di OTBR (didokumentasikan di sini). Anda dapat menggunakan browser, curl, atau wget untuk memeriksa isinya. Di antara banyak jalur yang didukung adalah kasus penggunaan yang dijelaskan di atas, dengan kata kerja GET di /node/dataset/active dan kata kerja PUT di /node/dataset/pending.

3. Menyiapkan Credential Framework di Android

Kredensial yang Disukai

Layanan Google Play di Android mengizinkan dan mengharapkan pendaftaran kredensial untuk semua TBR di jaringan Anda. Setiap perangkat diidentifikasi berdasarkan ID Agen Router Batasnya (BAID). 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 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 setelah pengguna memberikan izin. Sebaiknya Anda selalu memperbarui kredensial yang disimpan di layanan Google Play saat jaringan Thread Border Router diupdate. Meskipun informasi tersebut tidak digunakan saat ini, kami dapat menyediakan perjalanan yang lebih baik pada masa mendatang.

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

Sinkronisasi TBR Google

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

Proses ini juga akan terjadi saat 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 ada dua perangkat Android (atau Android + iGHA) di jaringan yang sama dengan set kredensial pilihan yang berbeda, perangkat yang awalnya mengonfigurasi TBR akan berlaku di TBR.

Pengaktifan TBR pihak ketiga

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

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

  • Jika ada jaringan pilihan, vendor harus menggunakannya. Tindakan ini memastikan bahwa perangkat Thread terhubung ke satu jaringan Thread jika memungkinkan.
  • Jika tidak ada jaringan pilihan, buat set kredensial baru dan tetapkan ke TBR Anda di Layanan Google Play. Android akan menggunakan kredensial tersebut sebagai kredensial standar yang ditetapkan di semua TBR berbasis Google, dan vendor lain akan dapat meningkatkan jangkauan dan keandalan mesh Anda dengan perangkat tambahan

cd8bc726f67b1fa1.png

4. Meng-clone dan Memodifikasi Aplikasi Android Anda

Kami telah membuat Aplikasi Android yang menunjukkan 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 di aplikasi contoh. Anda diundang untuk mengubahnya sesuai kebutuhan Anda, tetapi Anda cukup meng-clone aplikasi atau menjalankan biner yang telah dibuat sebelumnya untuk memeriksa fungsionalitasnya.

  1. Buat 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 di-clone.
  3. Aktifkan mode developer di ponsel Android Anda.
  4. Hubungkan ke komputer Anda melalui kabel USB.
  5. Jalankan Aplikasi dari Android Studio melalui <Cmd+R> (OS X) atau <Ctrl+R> (Win, Linux)
  6. Buka Roda -> Utilitas Developer -> Jaringan Thread
  7. Berinteraksi dengan berbagai opsi yang tersedia. Di bagian di bawah, kita akan menguraikan kode yang dieksekusi di setiap tombol.

Apakah kredensial pilihan ada?

Pertanyaan pertama yang harus diajukan produsen TBR kepada Google adalah apakah setelan kredensial pilihan sudah ada di perangkat. Ini harus menjadi titik awal alur Anda. Kode di bawah ini membuat kueri GPS tentang keberadaan kredensial. Pengguna tidak diminta memberikan izin 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 ingin membaca kredensial tersebut. Satu-satunya perbedaan dari kode sebelumnya adalah bahwa setelah menerima intentSenderResult, Anda ingin membuat dan meluncurkan intent menggunakan hasil tersebut dari pengirim.

Dalam kode kita, untuk tujuan organisasi/arsitektur, kita menggunakan MutableLiveData<IntentSender?> karena kode aslinya ada di ViewModel (ThreadViewModel.kt) dan pengamat intent ada di Fragment Aktivitas (ThreadFragment.kt). Jadi, setelah intentSenderResult diposting ke data aktif, kita akan menjalankan konten observer 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 untuk berbagi kredensial, 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.

Menetapkan kredensial GPS

Terlepas dari ada atau tidaknya, Anda harus mendaftarkan TBR 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 set Kredensial Pilihan. Informasi tersebut dapat diakses oleh Aplikasi apa pun 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 bersifat eksklusif untuk setiap vendor, dan dalam codelab ini kita mengimplementasikannya dengan DBUS+Python HTTP Rest Server atau HTTP Rest Server bawaan 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 Kata Kerja HTTP GET untuk mendapatkan kredensial dari TBR Anda. Lihat contoh skrip Python.

Build dan Impor

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

Perhatikan bahwa aplikasi contoh kami terutama dibuat untuk penyiapan Matter. Oleh karena itu, file Manifest dan Gradle-nya lebih kompleks daripada penambahan yang diperlukan hanya untuk 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 masing-masing.

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

6. Rangkuman

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

91979bf065e9673d.png

Urutan yang mungkin untuk mengaktifkan TBR Anda adalah:

  1. Kueri apakah kredensial preferensial ada (biru, baris ke-1)
  2. Bergantung pada jawabannya
  3. Mendapatkan kredensial pilihan GPS (biru, baris ke-2)
  4. Setel kredensial TBR di GPS (biru, baris ke-3) -> Pilih TBR Anda -> Buat Acak -> Masukkan nama jaringan -> Oke
  5. Setelah memiliki kredensial pilihan, tetapkan kredensial tersebut ke OTBR menggunakan Setel kredensial OTBR RPi, yang akan menerapkan kredensial tersebut ke set yang tertunda.

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

7. Kesimpulan

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

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

Semoga codelab dan Aplikasi Contoh ini membantu Anda mendesain dan mengembangkan Aplikasi Anda sendiri serta produk Thread Border Router Anda.

8. Referensi

Koprosesor RCP

DBUS