Android 適用的 Thread Network SDK

Thread Network SDK 提供類似數位鑰匙圈的功能,可讓 Android 應用程式與 Google Play 服務共用 Thread 網路憑證。這樣一來,應用程式就能從任何智慧住宅生態系統設定任何 Thread 裝置,不必直接公開憑證和使用者資料。

只要呼叫幾次 API,就能執行下列操作:

  1. 向 Google Play 服務要求偏好的 Thread 網路憑證。
  2. 設定新的 Thread Border Router (TBR),並將 Thread 網路憑證新增至 Google Play 服務。
  3. 如果已有現場 TBR,可以檢查 TBR 是否位於偏好的電視網,並視需要遷移。

請考慮幾種使用者和開發人員歷程。本指南將介紹大部分功能,以及其他重要功能和建議用法。

重要術語和 API 概念

開始之前,建議您先瞭解下列用語:

  • Thread 網路憑證:Thread TLV 的二進位大型物件,可編碼 Thread 網路名稱、網路金鑰和其他屬性,Thread 裝置必須具備這些屬性才能加入特定 Thread 網路。

  • 偏好的 Thread 網路憑證:系統自動選取的 Thread 網路憑證,可使用 getPreferredCredentials API 與不同供應商的應用程式共用。

  • 邊界代理 ID:裝置的 16 位元組全域專屬 ID。TBR這個 ID 由border router供應商建立及管理。

  • TBR 設定應用程式:這是 Android 應用程式,可設定新的TBR裝置,並將 Thread 網路憑證新增至 Google Play 服務。應用程式是所新增憑證的授權擁有者,並可存取這些憑證。

許多 Thread 網路 API 會傳回以非同步方式完成的 Task。您可以使用 addOnSuccessListeneraddOnFailureListener 註冊回呼,以接收結果。詳情請參閱「工作」說明文件。

憑證擁有權和維護

新增 Thread 網路憑證的應用程式會成為憑證擁有者,並具備憑證的完整存取權。如果您嘗試存取其他應用程式新增的憑證,就會收到 PERMISSION_DENIED 錯誤訊息。

建議應用程式擁有者在 TBR 網路更新時,一併更新 Google Play 服務中儲存的憑證。也就是說,您必須在需要時新增憑證、在 border router 的 Thread 網路憑證變更時更新憑證,以及在移除 TBR 或將其恢復原廠設定時移除憑證。

探索邊界代理程式

憑證必須以 Border Agent ID 儲存。請務必確認TBR設定應用程式可以判斷TBR的邊界代理程式 ID。

TBR 必須使用 mDNS 宣傳 Thread 網路資訊,包括網路名稱、擴充 PAN ID 和邊界代理程式 ID。這些屬性對應的 txt 值分別為 nnxpid

對於具有 Google Thread Border Router (gTBR) 的網路,Google Play 服務會自動取得 Google Thread 網路憑證以供使用。

將 SDK 整合到 Android 應用程式中

如要開始使用,請完成下列步驟:

  1. 按照「設定 Google Play 服務」一文的指示操作。

  2. build.gradle 檔案中新增 Google Play 服務依附元件:

    implementation 'com.google.android.gms:play-services-threadnetwork:16.2.1'
    
  3. 選用:定義 BorderAgent 資料類別,以儲存TBR資訊。我們會在整個指南中使用這項資料:

    data class BorderAgentInfo(
      // Network Name max 16 len
      val networkName: String = "",
      val extPanId: ByteArray = ByteArray(16),
      val borderAgentId: ByteArray = ByteArray(16),
      ...
    )
    

接著,我們會說明新增及管理偏好憑證的建議步驟。

設定新的 Thread 邊界路由器

為新的邊界路由器建立新網路前,請務必先嘗試使用偏好的網路憑證。確保 Thread 裝置盡可能連上單一 Thread 網路。

系統會透過 getPreferredCredentials 啟動活動,提示使用者允許網路要求。如果網路憑證已儲存在 Thread SDK 數位鑰匙圈中,系統會將憑證傳回應用程式。

要求憑證

如要提示使用者提供偏好的憑證:

  1. 宣告 ActivityLauncher

    private lateinit var preferredCredentialsLauncher: ActivityResultLauncher<IntentSenderRequest>
    
  2. 處理以 ThreadNetworkCredentials 形式傳回的 Activity 結果:

    preferredCredentialsLauncher =
     registerForActivityResult(
       StartIntentSenderForResult()
     ) { result: ActivityResult ->
       if (result.resultCode == RESULT_OK) {
         val threadNetworkCredentials = ThreadNetworkCredentials.fromIntentSenderResultData(result.data!!)
         Log.d("debug", threadNetworkCredentials.networkName)
       } else {
         Log.d("debug", "User denied request.")
       }
     }
    
  3. 如果您要設定新的 TBR,建議您呼叫 preferredCredentials 並啟動 Activity。這通電話可確保新 TBR 使用手機中已儲存的偏好憑證,促進不同 TBR 匯聚到相同網路。

    private fun getPreferredThreadNetworkCredentials() {
      ThreadNetwork.getClient(this)
        .preferredCredentials
      .addOnSuccessListener { intentSenderResult ->
        intentSenderResult.intentSender?.let {
          preferredCredentialsLauncher.launch(IntentSenderRequest.Builder(it).build())
          } ?: Log.d("debug", "No preferred credentials found.")
        }
      .addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
    }
    
  4. 如果您的用途是設定非 TBR 裝置 (例如新的 Matter over Thread 終端裝置),建議使用 allActiveCredentials API 擷取憑證。這項呼叫會掃描本機網路中找到的 TBR,因此不會傳回本機現有 TBR 無法提供的憑證。

    // Creates the IntentSender result launcher for the getAllActiveCredentials API
    private val getAllActiveCredentialsLauncher =
      registerForActivityResult(
        StartIntentSenderForResult()
      ) { result: ActivityResult ->
        if (result.resultCode == RESULT_OK) {
          val activeCredentials: List<ThreadNetworkCredentials> =
            ThreadNetworkCredentials.parseListFromIntentSenderResultData(
              result.data!!
            )
          // Use the activeCredentials list
        } else {
          // The user denied to share!
        }
      }
    
    // Invokes the getAllActiveCredentials API and starts the dialog activity with the returned
    // IntentSender
    threadNetworkClient
    .getAllActiveCredentials()
    .addOnSuccessListener { intentSenderResult: IntentSenderResult ->
      val intentSender = intentSenderResult.intentSender
      if (intentSender != null) {
        getAllActiveCredentialsLauncher.launch(
          IntentSenderRequest.Builder(intentSender).build()
        )
      } else {
        // No active network credentials found!
      }
    }
    // Handles the failure
    .addOnFailureListener { e: Exception ->
      // Handle the exception
    }
    

建立新的 Thread 網路

如果使用者 Thread 網路中沒有偏好的 Thread 網路憑證,也沒有有效的 Thread 憑證,則可以使用 addCredentials API 將憑證新增至 Google Play 服務。如要這麼做,您需要建立 ThreadBorderAgent, 並提供 ThreadNetworkCredentials 物件。

如要建立隨機網路,請呼叫 newRandomizeBuilder

val threadCredentials = ThreadNetworkCredentials.newRandomizedBuilder().build()

如要指定 Thread 網路名稱,請按照下列步驟操作:

val threadCredentials = ThreadNetworkCredentials.newRandomizedBuilder()
  .setNetworkName("ThreadNetworkSDK")
  .build()

新增憑證

如要讓其他 Thread 供應商使用你的 Thread 網路憑證,我們必須將憑證新增至 Google Play 服務。新增憑證前,我們也需要知道這個 Thread 網路屬於哪個 TBR 裝置。

在本例中,我們會從 Border Agent ID 建立 ThreadBorderAgent,並傳遞您剛建立的新 Thread 網路憑證:

private fun addCredentials(borderAgentInfo: BorderAgentInfo, credentialsToBeAdded: ThreadNetworkCredentials) {

  val threadBorderAgent = ThreadBorderAgent.newBuilder(borderAgentInfo.borderAgentId).build()
  Log.d("debug", "border router id:" + threadBorderAgent.id)

  ThreadNetwork.getClient(this)
    .addCredentials(threadBorderAgent, credentialsToBeAdded)
      .addOnSuccessListener {
        Log.d("debug", "Credentials added.")
      }
      .addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
}

偵測並遷移現場的 border router

如果您有 border router,可以使用 isPreferredCredentials 判斷 border router 是否屬於偏好網路。這個 API 不會提示使用者授權,而是根據 Google Play 服務中儲存的內容檢查 border router 憑證。

isPreferredCredentials 會以 Int 資料類型傳回 0 (不相符) 和 1 (相符)。你可以使用 IsPreferredCredentialsResult 查看結果。

public @interface IsPreferredCredentialsResult {
    int PREFERRED_CREDENTIALS_NOT_FOUND = -1;
    int PREFERRED_CREDENTIALS_NOT_MATCHED = 0;
    int PREFERRED_CREDENTIALS_MATCHED = 1;
}

如要使用 isPreferredCredentials,請先建立 ThreadNetworkCredentials 物件。執行個體化 ThreadNetworkCredentials 的方式有很多種,我們會在後續步驟中說明這些選項。

依作業資料集劃分的 Thread 網路憑證

有時你可能已使用 Thread 網路設定 TBR,並想將這個 Thread 網路新增至 Google Play 服務,以便與其他供應商共用。您可以從原始的 Thread Active Operational Dataset TLV 清單建立執行個體:ThreadNetworkCredential

  1. 將 Operational Dataset 轉換為 ByteArray。例如:

    val activeDataset =
          "0e080000000000010000000300000f35060004001fffe0020833333333...".dsToByteArray()
    
    fun String.dsToByteArray(): ByteArray {
      return chunked(2).map { it.toInt(16).toByte() }.toByteArray()
    }
    
  2. 使用 fromActiveOperationalDataset 建立 ThreadNetworkCredentials。成功後,你就能取得 Thread 網路名稱、頻道和其他網路資訊。如需完整屬性清單,請參閱 ThreadNetworkCredentials

    val threadNetworkCredentials =
        ThreadNetworkCredentials.fromActiveOperationalDataset(activeDataset)
    Log.d(
        "threadNetworkCredentials",
        threadNetworkCredentials.channel.toString() + " - " + threadNetworkCredentials.networkName)
    
  3. 呼叫 isPreferredCredentials API 並傳遞 ThreadNetworkCredentials

    ThreadNetwork.getClient(this)
    .isPreferredCredentials(threadNetworkCredentials)
    .addOnSuccessListener { result ->
      when (result) {
        IsPreferredCredentialsResult.PREFERRED_CREDENTIALS_NOT_MATCHED ->
            Log.d("isPreferredCredentials", "Credentials not matched.")
        IsPreferredCredentialsResult.PREFERRED_CREDENTIALS_MATCHED ->
            Log.d("isPreferredCredentials", "Credentials matched.")
      }
    }
    .addOnFailureListener { e: Exception -> Log.d("isPreferredCredentials", "ERROR: [${e}]") }
    

邊界代理程式的 Thread 網路憑證

邊界代理程式 ID 可用來區分TBR裝置。如要使用 getCredentialsByBorderAgent API,請先建立 ThreadBorderAgent 物件並傳遞邊界代理程式 ID。

建立 ThreadBorderAgent 物件後,請呼叫 getCredentialsByBorderAgent。如果已儲存憑證,請確認是否為偏好憑證。

private fun isPreferredThreadNetworkByBorderAgent(borderAgentInfo: BorderAgentInfo) {

  val threadBorderAgent = ThreadBorderAgent.newBuilder(borderAgentInfo.borderAgentId).build()
  Log.d("debug", "border router id:" + threadBorderAgent.id)

  var isPreferred = IsPreferredCredentialsResult.PREFERRED_CREDENTIALS_NOT_FOUND
  var borderAgentCredentials: ThreadNetworkCredentials?
  val taskByBorderAgent = ThreadNetwork.getClient(this)
  taskByBorderAgent
      .getCredentialsByBorderAgent(threadBorderAgent)
      .addOnSuccessListener { result: ThreadNetworkCredentialsResult ->
        borderAgentCredentials = result.credentials
        result.credentials?.let {
          taskByBorderAgent.isPreferredCredentials(it).addOnSuccessListener { result ->
            isPreferred = result
          }
        }
      }
      .addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
}

依擴充 PAN ID 顯示的 Thread 網路憑證

getPreferredCredentials 類似,您也可以提示使用者提供 TBR 的擴充 PAN ID 憑證。使用者核准後,getCredentialsByExtendedPanId 會傳回 IntentSender,而活動結果會包含 ThreadNetworkCredentials 物件。

private fun getCredentialsByExtPanId(borderAgentInfo: BorderAgentInfo) {
  ThreadNetwork.getClient(this)
    .getCredentialsByExtendedPanId(borderAgentInfo.extPanId)
    .addOnSuccessListener { intentSenderResult ->
      intentSenderResult.intentSender?.let {
        preferredCredentialsLauncher.launch(IntentSenderRequest.Builder(it).build())
      }
        ?: Log.d("debug", "No credentials found.")
    }
    .addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
}

移除憑證

從住家移除 border router 裝置或將裝置恢復原廠設定後,請從 Google Play 服務中移除裝置的 Thread 網路。

private fun removeCredentials(borderAgentInfo: BorderAgentInfo) {

  val threadBorderAgent = ThreadBorderAgent.newBuilder(borderAgentInfo.borderAgentId).build()
  Log.d("debug", "border router id:" + threadBorderAgent.id)

  ThreadNetwork.getClient(this)
      .removeCredentials(threadBorderAgent)
      .addOnSuccessListener { Log.d("debug", "Credentials removed.") }
      .addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
}

資源

如要進一步瞭解 Thread Network SDK,請參閱 API 參考資料