1. 始める前に
Thread ボーダー ルーター(TBR)の Codelab では、Raspberry Pi をベースにして Thread ボーダー ルーターを作成する方法を説明します。この Codelab では、
- Thread ネットワークと Wi-Fi/イーサネット ネットワークの間に双方向の IP 接続を確立します。
- mDNS(Wi-Fi/イーサネット リンク上)と SRP(Thread ネットワーク上)を介して双方向のサービス ディスカバリを提供します。
この Codelab は前の Codelab をベースにしており、独自のボーダー ルーターとアプリで Google API とやり取りして 1 つの Thread ネットワークを作成する方法を説明します。Thread の認証情報の集約は、ネットワークの堅牢性を高め、Thread に依存するアプリケーションとのユーザー操作を簡素化するうえで重要です。
前提条件
- OTBR Codelab を修了する
- Linux、Android/Kotlin、Thread ネットワークに関する基本的な知識
学習内容
- 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 サーバーを設定し、Thread の認証情報を取得する GET リクエストか、Thread の認証情報を設定する 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>
このスクリプトは、DBUS R/W コマンドをバスパス io.openthread.BorderRouter.wpan0
、オブジェクト パス /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 は、その機能のイントロスペクションを可能にします。これは次のように行うことができます。
$ 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 ボーダー ルーターは、デフォルトで 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
を使用して内容を確認できます。サポートされる多くのパスの中には、上記のユースケースがあります。GET
動詞は /node/dataset/active
に、PUT
動詞は /node/dataset/pending
にあります。
3. Android での認証情報フレームワークのセットアップ
推奨クルデンシャル
Android 版 Google Play 開発者サービスは、ネットワーク内のすべての TBR の認証情報の登録を許可しており、登録を想定しています。各 ID は Border Router Agent ID(BAID)で識別されます。このタスクを実行するには、ThreadNetworkClient
インターフェースの addCredentials()
メソッドを使用します。Google Play 開発者サービスのストレージに最初に追加された TBR によって、このモバイル デバイスの優先認証情報が決定されます。
Thread ネットワーク認証情報のセットを BAID に追加するアプリが、認証情報の所有者になり、認証情報にアクセスする完全な権限を持ちます。他のアプリによって追加された認証情報にアクセスしようとすると、PERMISSION_DENIED エラーが発生します。ただし、優先認証情報は、ユーザーの同意があればすべてのアプリで常に使用できます。Thread ボーダー ルーター ネットワークを更新する際に、Google Play 開発者サービスに保存されている認証情報を最新の状態に保つことをおすすめします。この情報は現時点では使用されていませんが、将来的には改善されたプロセスを提供する可能性があります。
最初の TBR が後で除外された場合でも、優先認証情報は Android デバイスに保持されます。設定すると、Thread 認証情報を管理する他のアプリは、getPreferredCredentials()
呼び出しから認証情報を取得できます。
Google TBR 同期
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 オンボーディング
認証情報のストレージは現在、ユーザーのスマートホーム(ストラクチャ)のスコープではありません。各 Android デバイスには BAID ストレージがありますが、ネットワークに Google TBR があると、Google Home App 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(Windows、Linux)
- ホイールに移動 ->開発者向けユーティリティ ->Thread ネットワーク
- 利用可能なさまざまなオプションを操作します。以下のセクションでは、各ボタンで実行されるコードについて説明します。
優先認証情報は存在するか
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
を受け取った後に、送信者からの結果を使用してインテントをビルドして起動したい点です。
このコードでは、組織/アーキテクチャの目的で MutableLiveData<IntentSender?>
を使用します。これは、元のコードが ViewModel(ThreadViewModel.kt)にあり、インテント オブザーバーが Activity Fragment(ThreadFragment.kt)にあるためです。そのため、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 のボーダー エージェント ID に関連付けられた認証情報を読み取ることができるのはアプリだけです。ただし、TBR が最初に登録された場合、これらの認証情報は [Preferred Credentials] セットにコピーされます。この情報は、ユーザーが承認していれば、スマートフォン上のどのアプリからもアクセスできます。
/** * 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 Server または OTBR のネイティブ HTTP REST Server を使用して実装します。
/** * 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 プロダクトから認証情報を取得する
前述のように、GET HTTP 動詞を使用して TBR から認証情報を取得します。Python スクリプトのサンプルをご覧ください。
ビルドとインポート
Android アプリを作成する際は、Google Play 開発者サービスの Thread モジュールをサポートするように、マニフェスト、ビルド、インポートに変更を加える必要があります。以下の 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 Service Discovery ライブラリ NSDManager を使用しています。完全なソースコードは ServiceDiscovery.kt
のサンプルアプリで確認できます。
6. すべてを 1 つに統合
これらの呼び出しを実装するかサンプルアプリを使用すると、RPi OTBR を完全にオンボーディングできます。このサンプルアプリでは、次の 8 つのボタンを公開します。
TBR のオンボーディング シーケンスは次のとおりです。
- 優先認証情報が存在するかどうかをクエリする(青、1 行目)
- 回答に応じて
- GPS 推奨認証情報を取得する(青、2 行目)
- GPS で TBR 認証情報を設定する(青、3 行目)->TBR を選択 ->ランダムに作成 ->ネットワーク名を入力 ->まあまあ
- 優先認証情報が用意できたので、[Set RPi OTBR credentials] を使用してその認証情報を OTBR に設定します。これで、これらの認証情報が保留セットに適用されます。
サンプルアプリでは、デフォルトで 10 秒の遅延が使用されます。したがって、この期間が経過すると、RPi TBR(およびネットワーク上に存在する他のノード)の認証情報が新しいデータセットに移行されます。
7. まとめ
この Codelab では、サンプルの Android アプリのクローンを作成し、Google Play 開発者サービスのThread Storage APIこれらの API を使用して、RPi TBR にオンボーディングできる共通のデータセットを作成しました。これにより、ベンダーの TBR が表示されます。
ユーザーの TBR をすべて同じネットワークに配置することで、Thread ネットワークの復元力とリーチが向上します。また、アプリが認証情報にアクセスできないために Thread デバイスをオンボーディングできないユーザー ジャーニーの欠陥も防ぎます。
この Codelab とサンプルアプリが、独自のアプリや Thread ボーダー ルーター製品の設計と開発に役立つことを願っています。
8. 参照文献
RCP コプロセッサ
DBUS