Android 版 Thread Network SDK

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

Thread Network SDK 提供类似于数字钥匙串的功能,让您的 Android 应用能够与 Google Play 服务共享 Thread 网络凭据。这样,您的应用就可以从任何智能家居生态系统设置任何 Thread 设备,而无需直接公开凭据和用户数据。

只需几次 API 调用,您就可以:

  1. 向 Google Play 服务请求首选线程网络凭据。
  2. 设置新的边界路由器并将您的 Thread 网络凭据添加到 Google Play 服务。
  3. 如果您已有现场边界路由器,则可以检查边界路由器是否位于首选网络中并根据需要迁移这些路由器。

用户和开发流程有几个方面需要考虑。我们将对本指南的大部分内容进行介绍,并介绍其他关键功能和建议的使用方式。

关键术语和 API 概念

开始之前,了解以下术语会很有帮助:

  • Thread 网络凭据:Thread TLV 的二进制 blob,用于对线程网络名称、网络密钥以及线程设备加入给定线程网络所需的其他属性进行编码。

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

  • 边界代理 ID:线程边界路由器设备的 16 字节全局唯一 ID。此 ID 由边界路由器供应商创建和管理。

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

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

凭据所有权和维护

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

作为应用所有者,我们建议您在更新线程边界路由器网络时使 Google Play 服务中存储的凭据保持最新状态。这意味着在需要时添加凭据,在边界路由器的线程网络凭据发生更改时更新凭据,并在线程边界路由器被移除或恢复出厂设置时移除凭据。

边框代理发现

必须使用边界代理 ID 保存凭据。您需要确保线程边界路由器设置应用能够确定线程边界路由器的边界代理 ID。

线程边界路由器必须使用 mDNS 通告线程网络信息,包括网络名称、扩展平移 ID 和边界代理 ID。这些属性的相应 txt 值分别为 nnxpid

对于具有 Google 边界路由器的网络,Google Play 服务会自动获取 Google Thread 网络凭据以供使用。

将 SDK 集成到 Android 应用中

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

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

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

    implementation 'com.google.android.gms:play-services-threadnetwork:16.0.0-beta02'
    
  3. 可选:定义用于存储边界路由器信息的 BorderAgent 数据类。在本指南中,我们将使用这些数据:

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

接下来,我们将介绍添加和管理首选凭据的建议步骤。

新的边界路由器设置

在为新边界路由器创建新网络之前,请务必先尝试使用首选网络凭据。这样可以确保线程设备尽可能连接到单个线程网络。

调用 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. 调用 preferredCredentials 并启动 activity:

    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}]") }
    }
    

创建新的 Thread 网络

如果用户的 Thread 网络中没有首选 Thread 网络凭据,那么您可以使用 addCredentials API 将凭据添加到 Google Play 服务。为此,您需要创建一个 ThreadBorderAgent,同时还要提供一个 ThreadNetworkCredentials 对象。

如需创建随机网络,请调用 newRandomizeBuilder

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

如需指定线程网络名称,请执行以下操作:

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

添加凭据

为了让您的 Thread 网络凭据可供其他 Thread 供应商使用,我们需要将其添加到 Google Play 服务中。在添加新的凭据之前,我们还需要知道此 Thread 网络属于哪个边界路由器设备。

在此示例中,我们将通过边界代理 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}]") }
}

检测和迁移实地边界路由器

如果您当前拥有实地边界路由器,则可以使用 isPreferredCredentials 确定您的边界路由器是否属于首选网络。此 API 不会提示用户授予权限,并会根据 Google Play 服务中存储的内容来检查边界路由器凭据。

isPreferredCredentails 可为不匹配返回 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 网络,并且您希望将此 Thread 网络添加到 Google Play 服务以与其他供应商共享。您可以基于原始线程活跃操作数据集 TLV 列表创建 ThreadNetworkCredential 实例:

  1. 将操作数据集转换为 ByteArray。例如:

    val activeDataset =
          "0e080000000000010000000300000f35060004001fffe0020833333333...".dsToByteArray()
    
    fun String.dsToByteArray(): ByteArray {
      return chunked(2).map { it.toInt(16).toByte() }.toByteArray()
    }
    
  2. 使用 fromActiveOperationalDataset 创建 ThreadNetworkCredentials。 成功后,您将能够获取线程网络名称、渠道和其他网络信息。如需查看完整的属性列表,请参阅 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}]") }
    

通过边界代理处理网络凭据

边界代理 ID 是边界路由器设备的唯一标识。如需使用 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}]") }
}

通过扩展平移 ID 处理网络凭据

getPreferredCredentials 类似,您还可以提示用户输入来自边界路由器的扩展平移 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}]") }
}

移除凭据

从家庭网络或恢复出厂设置中移除边界路由器设备时,您需要从 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 参考文档