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。您可以使用 addOnSuccessListener 和 addOnFailureListener 註冊回呼,以接收結果。詳情請參閱「工作」說明文件。

憑證擁有權和維護

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

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

探索邊界代理程式

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

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

對於具有 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 參考資料。