1. 始める前に
Thread ボーダー ルーター(TBR) Codelab では、Raspberry Pi ベースの Thread ボーダー ルーターを構築する方法について説明します。この Codelab では次のことを行いました。
- Thread ネットワークと Wi-Fi/イーサネット ネットワークの間に双方向 IP 接続を確立します。
- mDNS(Wi-Fi/イーサネット リンク)と SRP(Thread ネットワーク)を介した双方向サービス検出を提供します。
この Codelab では、前回の Codelab を基に、独自の境界ルーターとアプリが Google API とやり取りして単一の Thread ネットワークを作成する方法について説明します。Thread 認証情報を統合することは、ネットワークの堅牢性を高め、Thread に依存するアプリケーションとのユーザー操作を簡素化するため重要です。
前提条件
- OTBR Codelab を完了する
- Linux、Android/Kotlin、スレッド ネットワーキングに関する基本的な知識
学習内容
- Thread Sharing API を使用して認証情報セットを取得、設定する方法
- Google のネットワークと同じ認証情報を使用して独自の OpenThread ボーダー ルーターをセットアップする方法
必要なもの
- Raspberry Pi 4 ボード、または Open Thread Border Router(OTBR)を実行している他の Linux ベースのボード
- 無線コプロセッサ(RCP)として IEEE 802.15.4 接続を提供するボード。さまざまな SoC ベンダーのリポジトリとその手順については、OpenThread GitHub ページをご覧ください。
2. HTTP サービスの設定
必要な最初の構成要素は、アクティブな認証情報を読み取り、保留中の認証情報を OTBR に書き込むことができるインターフェースです。TBR を構築する場合は、次の 2 つの例に示すように、独自の独自のメカニズムを使用します。最初のオプションは、DBUS を介して OTBR エージェントとローカルでインターフェースする方法を示しています。2 つ目のオプションは、OTBR 上に構築できる Rest API を利用します。
どちらの方法も安全ではなく、本番環境ではそのまま使用しないでください。ただし、ベンダーはいずれかの方法で暗号化を構築して本番環境で使用できます。また、独自のモニタリング サービスを拡張して、ループバック HTTP または本質的にローカルな DBUS 呼び出しを発行することもできます。
オプション 1: Python スクリプトでの DBUS と HTTP API
このステップでは、認証情報を読み取り、設定するための 2 つのエンドポイントを公開し、最終的に DBUS コマンドを呼び出す、ベーシックな HTTP サービスを作成します。
OTBR として機能する RPi に、Python 3 の依存関係をインストールします。
$ pip install dbus-python shlex json
スクリプトを次のように実行します。
$ sudo python credentials_server.py 8081 serving at port 8081
このサンプルでは、ポート 8081 に HTTP サーバーをセットアップし、スレッドの認証情報を取得する GET リクエストまたはスレッドの認証情報を設定する POST リクエストのいずれかをルートパスでリッスンします。ペイロードは常に TLV を含む JSON 構造です。
次の PUT リクエストは、/node/dataset/pending
パスを使用して、OTBR に新しい保留中のスレッド認証情報を設定します。この場合、保留中の認証情報は 10 秒で適用されます。
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 }
/node/dataset/active
への GET リクエストは、現在アクティブな認証情報を取得します。
GET /node/dataset/active Host: <IP>:8081 ContentType = "application/json" acceptMimeType = "text/plain" ... <TLV encoded Thread Dataset>
このスクリプトは、バスパス io.openthread.BorderRouter.wpan0
、オブジェクトパス /io/openthread/BorderRouter/wpan0
に対して DBUS R/W コマンドを呼び出します。
# 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 では、機能のイントロスペクションが可能です。次のようにします。
$ sudo dbus-send --system --dest=io.openthread.BorderRouter.wpan0 \ --type=method_call --print-reply /io/openthread/BorderRouter/wpan0 \ org.freedesktop.DBus.Introspectable.Introspect
サポートされている機能については、こちらのドキュメントもご覧ください。
オプション 2: OTBR エージェントのネイティブ HTTP Rest API
OpenThread Border Router は、デフォルトで REST_API=1
フラグを使用してビルドされ、REST API が有効になります。前の Codelab のビルドで REST API が有効になっていない場合は、そのフラグを使用して RPi に OTBR をビルドしてください。
$ REST_API=1 INFRA_IF_NAME=wlan0 ./script/setup
OTBR エージェントは、次のコマンドを実行して再起動できます。
$ sudo systemctl restart otbr-agent.service
エージェントはポート 8081 で HTTP サーバーを起動します。このサーバーにより、ユーザーまたはモニタリング プログラムは OTBR で多くのタスクを実行できます(こちらにドキュメントがあります)。ブラウザ、curl
、wget
を使用して内容を検査できます。サポートされている多くのパスには、/node/dataset/active
の GET
動詞と /node/dataset/pending
の PUT
動詞を使用する上記のユースケースが含まれます。
3. Android で認証情報フレームワークを設定する
推奨される認証情報
Android 版 Google Play 開発者サービスでは、ネットワーク内のすべての TBR の認証情報の登録が許可され、想定されています。各エージェントは、境界ルーター エージェント ID(BAID)で識別されます。このタスクを実行するには、ThreadNetworkClient
インターフェースの addCredentials()
メソッドを使用します。Google Play 開発者サービスのストレージに最初に追加された TBR によって、このモバイル デバイスの優先認証情報が決まります。
一連の Thread ネットワーク認証情報を BAID に追加したアプリは、認証情報のオーナーになり、認証情報にアクセスする完全な権限を持ちます。他のアプリによって追加された認証情報にアクセスしようとすると、PERMISSION_DENIED エラーが発生します。ただし、優先認証情報は、ユーザーの同意があれば、どのアプリでも常に使用できます。Thread Border Router ネットワークが更新された場合は、Google Play 開発者サービスに保存されている認証情報を最新の状態に保つことをおすすめします。現在のところ、この情報は使用されていませんが、今後、高度な経路を提供できるようになる可能性があります。
最初の TBR が後で除外された場合でも、優先認証情報は Android デバイスに保持されます。設定すると、Thread 認証情報を管理する他のアプリが getPreferredCredentials()
呼び出しから認証情報を取得できるようになります。
Google TBR Sync
Android デバイスは Google TBR と自動的に同期されます。Android に認証情報が存在しない場合、デバイスはネットワーク内の Google TBR から認証情報を抽出し、その認証情報が優先認証情報になります。TBR と Android デバイスの同期は、TBR が 1 人のユーザーとペア設定されている場合、または同じスマートホーム(ストラクチャ)に属する 2 人のユーザーとペア設定されている場合にのみ行われます。
このプロセスは、別の Google ユーザーが同じ組織に所属している場合に、そのユーザーが Android 版 GHA または iOS 版 GHA を使用している場合にも行われます。iOS 版 GHA の場合、優先認証情報が存在しない場合、優先認証情報は iOS ストレージに設定されます。
2 台の Android デバイス(または Android と iGHA)が同じネットワークに存在し、優先認証情報のセットが異なる場合、TBR は最初に TBR を構成したデバイスが優先されます。
サードパーティの TBR オンボーディング
認証情報のストレージは現在、ユーザーのスマートホーム(ストラクチャ)のスコープ外です。各 Android デバイスには BAID ストレージがありますが、ネットワーク内に Google TBR が存在すると、Google Home アプリ for iOS を実行している他の Android デバイスと iOS デバイスがその TBR と同期し、スマートフォンのストレージにローカル認証情報を設定しようとします。
新しい OOB TBR がネットワークを作成する前に、優先ネットワークが Android のストレージにすでに存在するかどうかを確認することが重要です。
- 優先ネットワークが存在する場合は、ベンダーはそれを優先する必要があります。これにより、可能であれば Thread デバイスが単一の Thread ネットワークに接続されるようになります。
- 優先ネットワークが存在しない場合は、新しい認証情報セットを作成し、Google Play 開発者サービスの TBR に割り当てます。Android では、これらの認証情報がすべての Google ベースの TBR に設定された標準認証情報として使用されます。他のベンダーは、追加のデバイスを使用してメッシュの範囲と堅牢性を強化できます。
4. Android アプリのクローンを作成して変更する
Thread API で可能な主な呼び出しを示す Android アプリを作成しました。これらのパターンはアプリで使用できます。この Codelab では、GitHub から Matter 用の Google Home サンプルアプリのクローンを作成します。
ここで示されているソースコードはすべて、サンプルアプリにすでにコード化されています。必要に応じて変更することもできますが、アプリのクローンを作成するか、ビルド済みのバイナリを実行して機能を検査することもできます。
- 次を使用してクローンを作成します。
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
- Android Studio をダウンロードして開きます。
- [ファイル] > [開く] をクリックし、クローンを作成したリポジトリを指定します。
- Android スマートフォンでデベロッパー モードを有効にします。
- USB ケーブルでパソコンに接続します。
- Android Studio から <Cmd+R>(OS X)または <Ctrl+R>(Win、Linux)でアプリを実行します。
- ホイール -> [Developer Utilities] -> [Thread Network] に移動します。
- 利用可能なさまざまなオプションを操作します。以降のセクションでは、各ボタンで実行されるコードを解剖します。
優先認証情報が存在するか
TBR メーカーが Google に最初に尋ねるべき質問は、デバイスに優先する認証情報がすでに存在するかどうかです。これがフローの開始点になります。以下のコードは、認証情報の存在について GPS にクエリを実行します。認証情報が共有されないため、ユーザーの同意を求めるメッセージは表示されません。
/** * 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) } }
GPS の優先認証情報の取得
存在する場合は、認証情報を読み取る必要があります。前のコードとの唯一の違いは、intentSenderResult
を受信した後、送信者からの結果を使用してインテントを作成して起動する点です。
コードでは、元のコードが ViewModel(ThreadViewModel.kt)にあり、インテント オブザーバーがアクティビティ フラグメント(ThreadFragment.kt)にあるため、組織やアーキテクチャの目的のために MutableLiveData<IntentSender?>
を使用しています。したがって、intentSenderResult
がライブデータに投稿されると、このオブザーバーの内容が実行されます。
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() } }
これにより、共有認証情報に関するユーザーの同意がトリガーされ、承認されると、以下の方法でコンテンツが返されます。
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, "") } }
認証情報を MutableLiveData<ThreadNetworkCredentials?>
に投稿する方法は次のとおりです。
GPS 認証情報の設定
存在するかどうかにかかわらず、TBR を Google Play 開発者サービスに登録する必要があります。TBR の Border Agent ID に関連付けられた認証情報を読み取れるのは、アプリのみです。ただし、TBR が最初に登録した場合、それらの認証情報は優先認証情報セットにコピーされます。この情報には、ユーザーが許可している限り、スマートフォン上のどのアプリからもアクセスできます。
/** * 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) } } }
TBR プロダクトに認証情報を設定する
この部分はベンダー固有のものであり、この Codelab では、DBUS+Python HTTP Rest サーバーまたは OTBR のネイティブ HTTP Rest サーバーを使用して実装します。
/** * 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() )
TBR プロダクトから認証情報を取得する
前述のように、TBR から認証情報を取得するには、GET HTTP 動詞を使用します。Python スクリプトの例をご覧ください。
ビルドとインポート
Android アプリを作成する際は、Google Play 開発者サービスのスレッド モジュールをサポートするように、マニフェスト、ビルド、インポートを変更する必要があります。次の 3 つのスニペットは、追加された機能のほとんどをまとめたものです。
サンプルアプリは主に Matter の設定用に作成されています。そのため、マニフェスト ファイルと Gradle ファイルは、スレッド認証情報の使用のみに必要な追加よりも複雑になります。
マニフェストの変更
<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'
関連するインポート
// 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. mDNS/SD 検出
サンプルアプリでは、mDNS/SD 検出を使用して、ネットワーク内で使用可能な Thread 境界ルーターのリストと、それぞれの BAID を作成します。
これは、TBR の情報を GPS 認証情報のストレージに入力する場合に非常に役立ちます。ただし、その使用方法は、この Codelab の範囲外です。Android サービス ディスカバリ ライブラリ NSDManager を使用しています。ソースコード全体は、サンプルアプリの ServiceDiscovery.kt
で入手できます。
6. すべてをまとめる
これらの呼び出しを実装するか、サンプルアプリを使用すると、RPi OTBR を完全にオンボーディングできます。このサンプルアプリには、次の 8 つのボタンがあります。
TBR をオンボーディングする際の順序は次のとおりです。
- 優先認証情報が存在するかどうかをクエリします(青、1 行目)
- 回答に応じて
- GPS の優先認証情報を取得する(青、2 行目)
- GPS で TBR 認証情報を設定(青色、3 行目)-> TBR を選択 -> ランダムに作成 -> ネットワーク名を入力 -> OK
- 優先する認証情報を設定したら、RPi OTBR 認証情報を設定して OTBR に設定します。これにより、保留中のセットに認証情報が適用されます。
サンプルアプリのデフォルトでは、10 秒の遅延を使用します。したがって、この期間が経過すると、RPi TBR の認証情報(およびそのネットワーク上に存在する可能性のある他のノード)が新しいデータセットに移行されます。
7. まとめ
この Codelab では、Android アプリのサンプルをクローン化し、Google Play 開発者サービスの Thread Storage API を使用するコード スニペットをいくつか分析しました。これらの API を使用して、ベンダーの TBR を示す RPi TBR にオンボーディングできる共通データセットを作成しました。
ユーザーのすべての TBR を同じネットワークに配置すると、Thread ネットワークの復元力とリーチが向上します。また、認証情報にアクセスできないためにアプリが Thread デバイスをオンボーディングできないという、不完全なユーザー ジャーニーを防ぐこともできます。
この Codelab とサンプルアプリが、独自のアプリと Thread Border Router 製品の設計と開発に役立てば幸いです。
8. 参照
RCP コプロセッサ
DBUS