1. 事前準備
在 Thread Border Router (TBR) 程式碼研究室中,我們會示範如何建構基於 Raspberry Pi 的 Thread Border Router。在本程式碼研究室中,我們
- 在 Thread 和 Wi-Fi/乙太網路之間建立雙向 IP 連線。
- 透過 mDNS (在 Wi-Fi/乙太網路連線) 和 SRP (在 Thread 網路) 提供雙向服務探索功能。
本程式碼研究室會延續先前的內容,說明如何讓您自己的邊界路由器和應用程式與 Google API 互動,建立單一 Thread Network。匯集 Thread 憑證非常重要,因為這可提升網路穩定性,並簡化使用者與依賴 Thread 的應用程式互動。
必要條件
- 完成 OTBR 程式碼研究室
- 具備 Linux、Android/Kotlin 和執行緒網路的基本知識
課程內容
- 如何使用 Thread Sharing API 取得及設定憑證集
- 如何設定自己的 OpenThread 邊界路由器,並使用與 Google 網路相同的憑證
軟硬體需求
- Raspberry Pi 4 板卡或其他執行 Open Thread Border Router (OTBR) 的 Linux 板卡
- 此板卡可做為無線電共用處理器 (RCP),提供 IEEE 802.15.4 連線功能。請參閱 OpenThread GitHub 頁面,查看不同 SoC 供應商的存放區清單和操作說明
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 要求 (用於擷取執行緒憑證) 或 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 讀寫指令,路徑為 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 Agent 原生 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 Border Router 網路時,將 Google Play 服務中儲存的憑證保持在最新狀態。雖然目前不會使用這些資訊,但我們日後可能會提供更完善的歷程。
即使稍後排除第一個 TBR,Android 裝置仍會保留偏好憑證。設定完成後,管理執行緒憑證的其他應用程式可能會從 getPreferredCredentials()
呼叫取得憑證。
Google TBR Sync
Android 裝置會自動與 Google TBR 同步。如果 Android 上沒有憑證,裝置會從網路中的 Google TBR 中擷取憑證,這些憑證就會成為偏好憑證。只有在 TBR 與單一使用者配對,或與同一個智慧型家居 (Structure) 中的兩位使用者配對時,才會同步處理 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。
- 按一下「File」>「Open」,然後指向複製的存放區。
- 在 Android 手機上啟用開發人員模式。
- 使用 USB 傳輸線將裝置連接至電腦。
- 透過 <Cmd+R> (OS X) 或 <Ctrl+R> (Win、Linux) 從 Android Studio 執行應用程式
- 依序前往「Wheel」->「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
後,您想使用傳送端的結果建構及啟動意圖。
在程式碼中,我們使用 MutableLiveData<IntentSender?>
來進行組織/架構,因為原始程式碼位於 ViewModel (ThreadViewModel.kt),而意圖觀察器位於活動片段 (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 是第一個註冊的應用程式,這些憑證會複製到「偏好憑證」集。只要使用者授權,任何手機應用程式都可以存取這項資訊。
/** * 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 伺服器或 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 產品取得憑證
如前所述,請使用 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」將這些憑證設為 OTBR,系統會將這些憑證套用至待處理的憑證組合。
範例應用程式的預設值為使用 10 秒的延遲時間。因此,在這個期間過後,RPi TBR 的憑證 (以及可能存在於其網路上的其他節點) 會遷移至新資料集。
7. 結語
在本程式碼研究室中,我們複製了 Android 應用程式範例,並分析了幾個使用 Google Play 服務的 Thread Storage API 程式碼片段。我們使用這些 API 取得常見的資料集,並將其導入 RPi TBR,以便展示供應商的 TBR。
將使用者的所有 TBR 放在同一個網路中,可提升 Thread 網路的復原力和觸及範圍。這也能避免使用者體驗出現瑕疵,例如應用程式無法登錄 Thread 裝置,因為應用程式無法存取憑證。
我們希望本程式碼研究室和應用程式範例能協助您設計及開發自己的應用程式和 Thread Border Router 產品。
8. 參考資料
RCP 輔助處理器
DBUS