1. 事前準備
在Thread 邊界路由器 (TBR) 程式碼研究室中,我們說明如何以 Raspberry Pi 為基礎建構 Thread 邊界路由器。在該程式碼研究室中,我們
- 在 Thread 和 Wi-Fi/乙太網路之間建立雙向 IP 連線。
- 透過 mDNS (在 Wi-Fi/乙太網路連結上) 和 SRP (在 Thread 網路上) 提供雙向服務探索。
本程式碼研究室以上一個為基礎,說明如何讓自己的邊界路由器和應用程式與 Google API 互動,建立單一 Thread 網路。合併 Thread 憑證非常重要,因為這能提升網路穩定性,並簡化使用者與依賴 Thread 的應用程式互動。
必要條件
- 完成 OTBR 程式碼研究室
- 具備 Linux、Android/Kotlin 和 Thread 網路的基本知識
課程內容
- 如何使用 Thread Sharing API 取得及設定憑證集
- 如何使用與 Google 網路相同的憑證,設定自己的 OpenThread 邊界路由器
軟硬體需求
- Raspberry Pi 4 開發板或其他執行 Open Thread Border Router (OTBR) 的 Linux 開發板
- 提供 IEEE 802.15.4 連線的電路板,做為無線電共同處理器 (RCP)。如要查看不同 SoC 供應商的存放區清單和相關操作說明,請前往 OpenThread Github 頁面
2. 設定 HTTP 服務
我們需要的第一個建構區塊是介面,可讓我們讀取有效憑證,並將待處理憑證寫入 OTBR。建立 TBR 時,請使用您自己的專屬機制,如下列兩個範例所示。第一個選項展示如何透過 DBUS 在本機與 OTBR 代理程式介接,第二個選項則利用可建構於 OTBR 的 Rest API。
這兩種方法都不安全,不應在正式環境中直接使用。不過,供應商可以針對任一方法建構加密機制,以便在實際工作環境中使用,您也可以擴充自己的監控服務,發出迴路 HTTP 或本質上屬於本機的 DBUS 呼叫。
選項 1:Python 指令碼上的 DBUS 和 HTTP API

這個步驟會建立基本 HTTP 服務,公開兩個端點來讀取及設定憑證,最終呼叫 DBUS 指令。
在做為 OTBR 的 RPi 上,安裝 Python 3 依附元件:
$ pip install dbus-python shlex json
執行指令碼,如下所示:
$ sudo python credentials_server.py 8081 serving at port 8081
這個範例會在通訊埠 8081 上設定 HTTP 伺服器,並在根路徑上監聽 GET 要求 (用於擷取 Thread 憑證) 或 POST 要求 (用於設定 Thread 憑證)。酬載一律為含有 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 邊界路由器預設會使用 REST_API=1 旗標建構,啟用 REST API。如果先前程式碼研究室中的建構作業未啟用 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 (BAID)。您將使用 ThreadNetworkClient 介面的 addCredentials() 方法執行這項工作。新增至 Google Play 服務儲存空間的第一個 TBR 會決定這個行動裝置的偏好憑證。
將一組 Thread 網路憑證新增至 BAID 的應用程式,會成為憑證擁有者,並擁有存取憑證的完整權限。如果您嘗試存取其他應用程式新增的憑證,系統會顯示 PERMISSION_DENIED 錯誤。不過,只要使用者同意,任何應用程式隨時都能取得偏好的憑證。建議您在更新 Thread 邊界路由器網路時,一併更新 Google Play 服務中儲存的憑證。雖然目前不會使用這類資訊,但我們可能會在日後提供更完善的歷程。
即使稍後排除第一個 TBR,偏好憑證仍會保留在 Android 裝置上。設定完成後,管理 Thread 憑證的其他應用程式可以透過 getPreferredCredentials() 呼叫取得憑證。
Google TBR Sync
Android 裝置會自動與 Google TBR 同步。如果 Android 上沒有憑證,裝置會從網路中的 Google TBR 擷取憑證,並將這些憑證設為偏好憑證。只有在 TBR 與單一使用者配對,或與同一智慧型住宅 (結構) 的兩位使用者配對時,TBR 才會與 Android 裝置同步。
如果其他 Google 使用者在同一個住家中,並使用 Android 版或 iOS 版 Google Home 應用程式,也會發生這種情況。如果是 iOS 版 GHA,系統會將偏好的憑證設定在 iOS 儲存空間中 (如果沒有偏好的憑證)。
如果同一個網路中有兩部 Android 裝置 (或 Android + iGHA),且偏好的憑證組合不同,則 TBR 會優先使用最初設定 TBR 的裝置。
第三方 TBR 新手上路
憑證的儲存空間目前並未依使用者的智慧住宅 (結構體) 劃分範圍。每個 Android 裝置都會有自己的 BAID 儲存空間,但網路中一旦有 Google TBR,其他 Android 裝置和執行 iOS 版 Google Home 應用程式的 iOS 裝置就會與該 TBR 同步,並嘗試在手機儲存空間中設定本機憑證。
新的 OOB TBR 建立網路前,請務必先檢查 Android 儲存空間中是否已有偏好的網路。
- 如有慣用網路,供應商應使用該網路。確保 Thread 裝置盡可能連上單一 Thread 網路。
- 如果沒有偏好的網路,請建立新的憑證集,並在 Google Play 服務中指派給 TBR。Android 會將這些憑證視為所有 Google TBR 上設定的標準憑證,其他供應商則可使用額外裝置,擴大網狀網路的涵蓋範圍並提升穩定性

4. 複製及修改 Android 應用程式
我們建立了一個 Android 應用程式,展示 Thread API 的主要可能呼叫。您可以在應用程式中使用這些模式。在本程式碼研究室中,我們將從 GitHub 複製 Google Home Sample App for Matter。
這裡顯示的所有原始碼都已編碼至範例應用程式中。歡迎您根據自身需求修改,但也可以直接複製應用程式或執行預先建構的二進位檔,檢查功能。
- 使用下列指令複製:
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
- 下載並開啟 Android Studio。
- 按一下「File」>「Open」,然後指向您複製的存放區。
- 在 Android 手機上啟用開發人員模式。
- 使用 USB 傳輸線將手機連接至電腦。
- 透過 <Cmd+R> (OS X) 或 <Ctrl+R> (Win、Linux) 從 Android Studio 執行應用程式
- 依序前往「輪子」->「開發人員公用程式」->「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 片段 (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。只有您的應用程式可以讀取與 TBR 邊界代理程式 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 產品的憑證
這部分屬於各供應商的專有技術,在本程式碼研究室中,我們會透過 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 服務執行緒模組。下列三個程式碼片段摘要說明瞭大部分的增補內容。
請注意,我們的範例應用程式主要用於 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 憑證儲存空間時,這項功能非常實用。不過,本程式碼研究室不會說明如何使用這項功能。我們使用 Android 服務探索程式庫 NSDManager,完整原始碼位於範例應用程式的 ServiceDiscovery.kt 中。
6. 綜合應用範例
實作這些呼叫或使用範例應用程式後,即可全面啟用 RPi OTBR。我們的範例應用程式會顯示 8 個按鈕:

以下是可能的 TBR 上線順序:
- 查詢是否有優先憑證 (藍色,第 1 列)
- 視答案而定
- 取得 GPS 偏好憑證 (藍色,第 2 列)
- 在 GPS 中設定 TBR 憑證 (藍色,第 3 列) -> 選取 TBR -> 建立隨機 -> 輸入網路名稱 -> 確定
- 現在您已取得偏好的憑證,請使用「Set RPi OTBR credentials」(設定 RPi OTBR 憑證) 將憑證設為 OTBR,這會將憑證套用至待處理的設定。
範例應用程式預設會使用 10 秒延遲。因此,在這段時間過後,RPi TBR 的憑證 (以及網路中可能存在的其他節點) 會遷移至新的資料集。
7. 結語
在本程式碼研究室中,我們複製了 Android 範例應用程式,並分析了使用 Google Play 服務的 Thread Storage API 的幾個程式碼片段。我們使用這些 API 取得通用資料集,以便在 RPi TBR 上導入,展示供應商的 TBR。
將使用者的所有 TBR 放在同一個網路上,可提升 Thread 網路的韌性和觸及率。此外,如果應用程式無法存取憑證,就無法讓使用者加入 Thread 裝置,這項功能也能避免這種有瑕疵的使用者歷程。
希望本程式碼研究室和範例應用程式能協助您設計及開發自己的應用程式和 Thread 邊界路由器產品。
8. 參考資料
RCP 輔助處理器
DBUS