SDK de Thread Network para Android

El SDK de Thread Network proporciona una funcionalidad similar a la de un llavero digital, lo que permite que tus apps para Android compartan credenciales de red Thread con los Servicios de Google Play. Esto permite que tus apps configuren cualquier dispositivo Thread desde cualquier ecosistema de casa inteligente, sin exponer las credenciales ni los datos del usuario de forma directa.

Con solo unas pocas llamadas a la API, puedes hacer lo siguiente:

  1. Solicita las credenciales de la red Thread preferida a los Servicios de Google Play.
  2. Configura nuevos Thread Border Router (TBR) y agrega tus credenciales de red de Thread a los Servicios de Google Play.
  3. Si ya tienes TBR en el campo, puedes verificar si tus TBR están en la red preferida y migrarlos, si es necesario.

Existen varios recorridos de usuarios y desarrolladores que se deben tener en cuenta. Abordaremos la mayoría de ellos en esta guía, junto con otras funciones clave y el uso recomendado.

Terminología clave y conceptos de la API

Antes de comenzar, es útil comprender los siguientes términos:

  • Credenciales de la red Thread: Es un BLOB binario de TLV de Thread que codifica el nombre de la red Thread, la clave de red y otras propiedades que requiere un dispositivo Thread para unirse a una red Thread determinada.

  • Credenciales de red de Thread preferidas: Son las credenciales de red de Thread seleccionadas automáticamente que se pueden compartir con apps de diferentes proveedores a través de la API de getPreferredCredentials.

  • ID del agente de borde: Es un ID único a nivel global de 16 bytes para un dispositivo TBR. Los proveedores de border router crean y administran este ID.

  • App de configuración de TBR: Es tu app para Android que configura dispositivos TBR nuevos y agrega las credenciales de la red de Thread a los Servicios de Google Play. Tu app es la propietaria autorizada de las credenciales agregadas y tiene acceso a ellas.

Muchas de las APIs de Thread Network devuelven una Task que se completa de forma asíncrona. Puedes usar addOnSuccessListener y addOnFailureListener para registrar devoluciones de llamada que reciban el resultado. Para obtener más información, consulta la documentación de Task.

Propiedad y mantenimiento de las credenciales

La app que agrega las credenciales de la red Thread se convierte en la propietaria de las credenciales y tiene permisos completos para acceder a ellas. Si intentas acceder a las credenciales que agregaron otras apps, recibirás un error PERMISSION_DENIED.

Como propietario de la app, se recomienda que mantengas actualizadas las credenciales almacenadas en los Servicios de Google Play cuando se actualice la red de TBR. Esto significa agregar credenciales cuando sea necesario, actualizarlas cuando cambien las credenciales de la red de Thread del border router y quitarlas cuando se quite el TBR o se restablezca la configuración de fábrica.

Descubrimiento de agentes de borde

Las credenciales se deben guardar con un ID de agente de borde. Deberás asegurarte de que tu app de configuración de TBR pueda determinar los IDs de agente de Border de tus TBR.

Los TBR deben usar mDNS para anunciar la información de la red Thread, incluidos el nombre de la red, el ID de PAN extendido y el ID del agente de borde. Los valores de txt correspondientes para estos atributos son nn, xp y id, respectivamente.

En el caso de las redes con Google Thread Border Router (gTBR), los Servicios de Google Play obtienen automáticamente las credenciales de la red de Google Thread para usarlas.

Integra el SDK en tu app para Android

Para comenzar, completa los siguientes pasos:

  1. Sigue las instrucciones que se proporcionan en Cómo configurar los Servicios de Google Play.

  2. Agrega la dependencia de los Servicios de Google Play a tu archivo build.gradle:

    implementation 'com.google.android.gms:play-services-threadnetwork:16.2.1'
    
  3. Opcional: Define una clase de datos BorderAgent para almacenar información de TBR. Usaremos estos datos en toda esta guía:

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

A continuación, revisaremos los pasos recomendados para agregar y administrar las credenciales preferidas.

Nuevas configuraciones de routers de borde Thread

Antes de crear una red nueva para los routers de borde nuevos, es importante que primero intentes usar las credenciales de red preferidas. Esto garantiza que los dispositivos Thread estén conectados a una sola red Thread cuando sea posible.

Una llamada a getPreferredCredentials inicia una actividad que solicita a los usuarios que permitan la solicitud de red. Si las credenciales de red se almacenaron en el llavero digital del SDK de Thread, se devolverán a tu app.

Solicita credenciales

Para solicitar al usuario las credenciales preferidas, haz lo siguiente:

  1. Declara un ActivityLauncher:

    private lateinit var preferredCredentialsLauncher: ActivityResultLauncher<IntentSenderRequest>
    
  2. Controla el resultado de la actividad, que se devuelve como ThreadNetworkCredentials:

    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. Si configuras un TBR nuevo, te recomendamos que llames a preferredCredentials y lances la actividad. Esta llamada garantizará que tu nuevo TBR use las mismas credenciales que ya se almacenaron como preferidas en el teléfono, lo que promoverá la convergencia de diferentes TBR en la misma red.

    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. Si tu caso de uso se relaciona con la configuración de dispositivos que no son TBR, como un nuevo dispositivo final de Matter sobre Thread, se recomienda que uses la API de allActiveCredentials para recuperar las credenciales. Esta llamada buscará TBRs que se encuentren en la red local y, por lo tanto, no devolverá credenciales que no estén disponibles por un TBR existente de forma local.

    // 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
    }
    

Crea una red Thread nueva

Si no hay credenciales de red de Thread preferidas ni credenciales de Thread activas disponibles en la red de Thread de un usuario, puedes usar la API de addCredentials para agregar credenciales a los Servicios de Google Play. Para ello, deberás crear un ThreadBorderAgent y, además, proporcionar un objeto ThreadNetworkCredentials.

Para crear una red aleatoria, llama a newRandomizeBuilder:

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

Para especificar el nombre de la red Thread, haz lo siguiente:

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

Agregar credenciales

Para que tus credenciales de red de Thread estén disponibles para otros proveedores de Thread, debemos agregarlas a los Servicios de Google Play. Antes de agregar nuestras credenciales nuevas, también debemos saber a qué dispositivo de TBR pertenece esta red de Thread.

En este ejemplo, crearemos un ThreadBorderAgent a partir de un ID de agente de borde y pasaremos las nuevas credenciales de la red de Thread que acabas de crear:

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

Detecta y migra border router en el campo

Si tienes border router en el campo, puedes usar isPreferredCredentials para determinar si tus border router pertenecen a la red preferida. Esta API no solicita permiso al usuario y compara las credenciales de border router con lo que se almacena en los Servicios de Google Play.

isPreferredCredentials devuelve 0 para las coincidencias y 1 para las no coincidencias, como un tipo de datos Int. Puedes usar IsPreferredCredentialsResult para verificar tus resultados.

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

Para usar isPreferredCredentials, primero deberás crear un objeto ThreadNetworkCredentials. Existen varias formas de crear una instancia de ThreadNetworkCredentials. En los próximos pasos, analizaremos estas opciones.

Credenciales de la red Thread por conjunto de datos operativos

Hay casos en los que tu TBR ya está configurado con una red de Thread y quieres agregar esta red de Thread a los Servicios de Google Play para compartirla con otros proveedores. Puedes crear una instancia de ThreadNetworkCredential a partir de una lista de TLV de conjunto de datos operativos activos de Thread sin procesar:

  1. Convierte el conjunto de datos operativos en un ByteArray. Por ejemplo:

    val activeDataset =
          "0e080000000000010000000300000f35060004001fffe0020833333333...".dsToByteArray()
    
    fun String.dsToByteArray(): ByteArray {
      return chunked(2).map { it.toInt(16).toByte() }.toByteArray()
    }
    
  2. Usa fromActiveOperationalDataset para crear el ThreadNetworkCredentials. Si se ejecuta correctamente, podrás obtener el nombre, el canal y otra información de la red de Thread. Para obtener una lista completa de las propiedades, consulta ThreadNetworkCredentials.

    val threadNetworkCredentials =
        ThreadNetworkCredentials.fromActiveOperationalDataset(activeDataset)
    Log.d(
        "threadNetworkCredentials",
        threadNetworkCredentials.channel.toString() + " - " + threadNetworkCredentials.networkName)
    
  3. Llama a la API de isPreferredCredentials y pasa el 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}]") }
    

Credenciales de red de Thread por agente de borde

Un ID de agente de borde identifica de forma única un dispositivo TBR. Para usar la API de getCredentialsByBorderAgent, primero deberás crear un objeto ThreadBorderAgent y pasar el ID del agente de borde.

Una vez que hayas creado el objeto ThreadBorderAgent, llama a getCredentialsByBorderAgent. Si se guardaron las credenciales, verifica si son las preferidas.

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

Credenciales de red de Thread por ID de PAN extendido

Al igual que con getPreferredCredentials, también puedes solicitarle al usuario las credenciales del ID de PAN extendido de un TBR. getCredentialsByExtendedPanId devuelve un IntentSender, y el resultado de la actividad contiene un objeto ThreadNetworkCredentials cuando el usuario lo aprueba.

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

Quita las credenciales

Cuando se quita tu dispositivo border router de tu casa o se restablece la configuración de fábrica, debes quitar su red Thread de los Servicios de Google Play.

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

Recursos

Para obtener más información sobre el SDK de Thread Network, consulta la referencia de la API.