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 共用 API 取得及設定憑證集
- 如何使用與 Google 網路相同的憑證設定您自己的 OpenThread 邊界路由器
軟硬體需求
- 執行 Open Thread 邊界路由器 (OTBR) 的 Raspberry Pi 4 主機板或其他 Linux 主機板
- 提供 IEEE 802.15.4 連線做為無線電合作處理器 (RCP) 的電路板。如要查看不同 SoC 供應商的存放區清單及相關操作說明,請前往 OpenThread GitHub 頁面
2. 設定 HTTP 服務
我們需要的第一個構成元素是介面,可讓我們讀取有效憑證,並將待處理的憑證寫入 OTBR。建構 TBR 時,請使用自己的專屬機制,如這裡有兩個範例所示。第一個選項展示如何透過 DBUS 在本機介面與 OTBR 代理程式互動,而第二個選項則會利用可在 OTBR 上建構的 Rest API。
這兩種方法都不安全,不應在正式環境中使用,不過,廠商可以針對任一方法建立加密機制,以便在正式環境中使用,或者您也可以擴充自己的監控服務,發出回回 HTTP 或固有本機 DBUS 呼叫。
選項 1:以 Python Script 執行 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 伺服器,並監聽根路徑 (用於擷取 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。如果先前程式碼研究室中的建構作業未啟用 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 邊界路由器網路更新時,將儲存在 Google Play 服務中的憑證保持在最新狀態。雖然這項資訊目前尚未使用,但未來我們可能會提供更完善的體驗。
即使稍後排除第一個 TBR,偏好的憑證仍會保留在 Android 裝置上。設定完成後,其他管理 Thread 憑證的應用程式可能會透過 getPreferredCredentials()
呼叫取得憑證。
Google TBR Sync
Android 裝置會自動與 Google TBR 保持同步。如果 Android 上沒有憑證,裝置會從您網路的 Google TBR 中擷取憑證,讓這些憑證成為偏好的憑證。TBR 必須與單一使用者配對,或將 TBR 與同一個智慧型住宅 ( 結構) 中的兩位使用者配對,系統才會同步處理 TBR 和 Android 裝置。
如有另一位 Google 使用者使用 Android 版 GHA 或 iOS 適用的 GHA,當使用者位於相同結構時,也會發生上述情況。在 iOS 適用的 GHA 中,如果沒有偏好的憑證,則系統會在 iOS 儲存空間上設定偏好的憑證。
如果兩部 Android 裝置 (或 Android + iGHA) 在同一個網路中,並使用不同的偏好憑證組合,原先設定 TBR 的裝置會在 TBR 上發揮作用。
第三方 TBR 新手上路
憑證儲存空間目前未受到使用者的智慧型住宅 ( 結構) 限定。每部 Android 裝置都會有 BAID 儲存空間,但只要網路中有 Google TBR,執行 Google Home 應用程式 iOS 版的其他 Android 裝置和 iOS 裝置就會與該 TBR 同步,並嘗試在手機儲存空間上設定本機憑證。
建立新的 OOB TBR 網路之前,請務必確認 Android 儲存空間中是否已有偏好的網路。
- 如果有偏好的網路,供應商應使用該網路,這可確保 Thread 裝置盡可能連線至單一 Thread 網路。
- 如果沒有偏好的網路,請建立新的憑證集,然後在 Google Play 服務中將該網路指派給 TBR。Android 會遵循這些憑證,成為所有 Google 型 TBR 中設定的標準憑證。其他供應商將能透過其他裝置,提升網狀觸及率和穩定性
4. 複製及修改 Android 應用程式
我們已建立 Android 應用程式,用於展示 Thread API 主要可能的呼叫。您可以在應用程式中使用這些模式。在本程式碼研究室中,我們會從 GitHub 複製適用於 Matter 的 Google Home 範例應用程式。
這裡顯示的所有原始碼都已在範例應用程式中完成編碼。您已獲邀根據自己的需求修改程式碼,但只要複製應用程式或執行預先建構的二進位檔,即可檢查這項功能。
- 使用下列指令複製檔案:
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
- 下載並開啟 Android Studio。
- 按一下 [檔案] >開啟並指向複製的存放區。
- 在 Android 手機上啟用開發人員模式。
- 使用 USB 傳輸線將裝置連接至電腦。
- 透過 <Cmd + R> 在 Android Studio 中執行應用程式(OS X) 或 <Ctrl+R> 鍵(Win、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 憑證
無論內容是否存在,您都應在 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 檔案比只使用 Thread 憑證所需新增更為複雜。
資訊清單變更
<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 ->建立隨機 ->輸入聯播網名稱 ->確定
- 現在,您已取得偏好的憑證,請透過設定 RPi OTBR 憑證將憑證設為 OTBR,這會將這些憑證套用至待處理組合。
範例應用程式的預設值為 10 秒延遲。因此,這段期間過後,RPi TBR 的憑證 (以及其網路上可能存在的其他節點) 將會遷移至新的資料集。
7. 結語
在本程式碼研究室中,我們複製了一個 Android 範例應用程式,並分析了使用 Google Play 服務的程式碼片段Thread Storage API。我們使用這些 API 建立了可新增至 RPi TBR 的通用資料集,從而展示廠商的 TBR。
將使用者所有的 TBR 都位於同一個網路,可提升 Thread 網路的韌性和覆蓋範圍。這麼做也能防止應用程式因無法存取憑證而無法安裝在 Thread 裝置上,造成不良的使用者歷程。
希望本程式碼研究室和範例應用程式有助於您設計及開發自己的應用程式和 Thread 邊界路由器產品。
8. 參考資料
RCP 輔助處理器
DBUS