Thread 网络 SDK 提供类似于数字钥匙串的功能,可让 Android 应用与 Google Play 服务共享 Thread 网络凭据。这样一来,您的应用就可以设置任何智能家居生态系统中的任何 Thread 设备,而无需直接公开凭据和用户数据。
只需进行几次 API 调用,您就可以:
- 从 Google Play 服务请求首选 Thread 网络凭据。
- 设置新的 Thread Border Router (TBR) 并将 Thread 网络凭据添加到 Google Play 服务。
- 如果您已有在野外部署的 TBR,可以检查这些 TBR 是否位于首选网络中,并根据需要迁移它们。
需要考虑多种用户和开发者历程。本指南将介绍其中大部分功能,以及其他关键功能和建议的用法。
关键术语和 API 概念
在开始之前,请先了解以下术语:
Thread 网络凭证:Thread TLV 的二进制 blob,用于对 Thread 网络名称、网络密钥以及 Thread 设备加入给定 Thread 网络所需的其他属性进行编码。
首选 Thread 网络凭据:使用
getPreferredCredentialsAPI 可与不同供应商的应用共享的自动选择的 Thread 网络凭据。边框代理 ID:TBR 设备的 16 字节全局唯一 ID。此 ID 由 border router 供应商创建和管理。
TBR 设置应用:这是您的 Android 应用,用于设置新的 TBR 设备并将 Thread 网络凭据添加到 Google Play 服务。您的应用是所添加凭据的权威所有者,并且有权访问这些凭据。
许多 Thread 网络 API 都会返回一个异步完成的 Task。您可以使用 addOnSuccessListener 和 addOnFailureListener 注册用于接收结果的回调。如需了解详情,请参阅任务文档。
凭据所有权和维护
添加 Thread 网络凭据的应用会成为该凭据的所有者,并拥有对该凭据的完整访问权限。如果您尝试访问其他应用添加的凭据,则会收到 PERMISSION_DENIED 错误。
作为应用所有者,建议您在 TBR 网络更新时,及时更新存储在 Google Play 服务中的凭据。这意味着在需要时添加凭据,在 border router 的 Thread 网络凭据发生更改时更新凭据,以及在移除 TBR 或将其恢复出厂设置时移除凭据。
边框代理发现
凭据必须与边框代理 ID 一起保存。您需要确保TBR设置应用能够确定TBR的 Border 代理 ID。
TBR必须使用 mDNS 来通告 Thread 网络信息,包括网络名称、扩展 PAN ID 和边框代理 ID。这些属性对应的 txt 值分别为 nn、xp 和 id。
对于具有 Google Thread Border Router (gTBR) 的网络,Google Play 服务会自动获取 Google Thread 网络凭据以供使用。
将 SDK 集成到 Android 应用中
如需开始使用,请完成以下步骤:
请按照设置 Google Play 服务中提供的说明操作。
将 Google Play 服务依赖项添加到
build.gradle文件中:implementation 'com.google.android.gms:play-services-threadnetwork:16.2.1'可选:定义一个
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 数字钥匙串中,则凭据会返回到您的应用。
请求凭据
提示用户提供首选凭据:
声明
ActivityLauncher:private lateinit var preferredCredentialsLauncher: ActivityResultLauncher<IntentSenderRequest>处理以
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.") } }如果您要设置新的 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}]") } }如果您的使用情形与设置非 TBR 设备(例如新的 Matter-over-Thread 终端设备)相关,建议您使用
allActiveCredentialsAPI 来获取凭据。此调用将扫描本地网络中找到的 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 实例:
将运营数据集转换为
ByteArray。例如:val activeDataset = "0e080000000000010000000300000f35060004001fffe0020833333333...".dsToByteArray()fun String.dsToByteArray(): ByteArray { return chunked(2).map { it.toInt(16).toByte() }.toByteArray() }使用
fromActiveOperationalDataset创建ThreadNetworkCredentials。成功后,您将能够获取 Thread 网络名称、频道和其他网络信息。如需查看属性的完整列表,请参阅 ThreadNetworkCredentials。val threadNetworkCredentials = ThreadNetworkCredentials.fromActiveOperationalDataset(activeDataset) Log.d( "threadNetworkCredentials", threadNetworkCredentials.channel.toString() + " - " + threadNetworkCredentials.networkName)调用
isPreferredCredentialsAPI 并传递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 参考文档。