适用于 Android 的 Thread 网络 SDK

Thread 网络 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 的二进制 blob,用于对 Thread 网络名称、网络密钥以及 Thread 设备加入给定 Thread 网络所需的其他属性进行编码。

  • 首选 Thread 网络凭据:使用 getPreferredCredentials API 可与不同供应商的应用共享的自动选择的 Thread 网络凭据。

  • 边框代理 IDTBR 设备的 16 字节全局唯一 ID。此 ID 由 border router 供应商创建和管理。

  • TBR 设置应用:这是您的 Android 应用,用于设置新的 TBR 设备并将 Thread 网络凭据添加到 Google Play 服务。您的应用是所添加凭据的权威所有者,并且有权访问这些凭据。

许多 Thread 网络 API 都会返回一个异步完成的 Task。您可以使用 addOnSuccessListeneraddOnFailureListener 注册用于接收结果的回调。如需了解详情,请参阅任务文档。

凭据所有权和维护

添加 Thread 网络凭据的应用会成为该凭据的所有者,并拥有对该凭据的完整访问权限。如果您尝试访问其他应用添加的凭据,则会收到 PERMISSION_DENIED 错误。

作为应用所有者,建议您在 TBR 网络更新时,及时更新存储在 Google Play 服务中的凭据。这意味着在需要时添加凭据,在 border router 的 Thread 网络凭据发生更改时更新凭据,以及在移除 TBR 或将其恢复出厂设置时移除凭据。

边框代理发现

凭据必须与边框代理 ID 一起保存。您需要确保TBR设置应用能够确定TBR的 Border 代理 ID。

TBR必须使用 mDNS 来通告 Thread 网络信息,包括网络名称、扩展 PAN ID 和边框代理 ID。这些属性对应的 txt 值分别为 nnxpid

对于具有 Google Thread Border Router (gTBR) 的网络,Google Play 服务会自动获取 Google Thread 网络凭据以供使用。

将 SDK 集成到 Android 应用中

如需开始使用,请完成以下步骤:

  1. 请按照设置 Google Play 服务中提供的说明操作。

  2. 将 Google Play 服务依赖项添加到 build.gradle 文件中:

    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 的调用会启动一个 Activity,提示用户允许网络请求。如果网络凭据已存储在 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 设备。

在此示例中,我们将从边境代理 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

如果您有 in-field border router,可以使用 isPreferredCredentials 来确定您的 border router 是否属于首选网络。此 API 不会提示用户授予权限,而是根据 Google Play 服务中存储的内容检查 border router 凭据。

isPreferredCredentials 会返回 0(表示不匹配)和 1(表示匹配),作为 Int 数据类型。您可以使用 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 网络凭证

在某些情况下,您的 TBR 已设置了 Thread 网络,您希望将此 Thread 网络添加到 Google Play 服务,以便与其他供应商共享。您可以从原始 Thread Active Operational Dataset TLV 列表创建 ThreadNetworkCredential 实例:

  1. 将运营数据集转换为 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,并且当用户批准时,Activity 结果会包含一个 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 参考文档