Cómo compartir la red Thread con las APIs de Google Thread Credentials

Acerca de este codelab
schedule44 minutos
subjectÚltima actualización: 23 de abril de 2025
account_circleEscrito por Doug Ferraz

1. Antes de comenzar

En nuestro codelab de router de borde de Thread (TBR), mostramos cómo crear un router de borde de Thread basado en una Raspberry Pi. En ese codelab, hicimos lo siguiente:

  • Establece conectividad de IP bidireccional entre Thread y redes Wi-Fi o Ethernet.
  • Proporciona el descubrimiento de servicios bidireccional a través de mDNS (en el vínculo Wi-Fi/Ethernet) y SRP (en la red Thread).

Este codelab se basa en el anterior y aborda cómo tu propio router de borde y tu app pueden interactuar con las APIs de Google para crear una sola red Thread. La convergencia de credenciales de Thread es importante, ya que aumenta la solidez de la red y simplifica las interacciones del usuario con las aplicaciones que dependen de Thread.

Requisitos previos

  • Completa el codelab de OTBR
  • Conocimientos básicos de Linux, Android/Kotlin y redes de Thread

Qué aprenderás

  • Cómo usar las APIs de Thread Sharing para obtener y configurar conjuntos de credenciales
  • Cómo configurar tu propio router de borde OpenThread con las mismas credenciales que la red de Google

Requisitos

  • Placa Raspberry Pi 4 o alguna otra placa basada en Linux que ejecute el router de borde Open Thread (OTBR)
  • Placa que proporciona conectividad IEEE 802.15.4 como un coprocesador de radio (RCP). Consulta una lista de repositorios de diferentes proveedores de SoC y sus instrucciones en la página de GitHub de OpenThread.

2. Cómo configurar el servicio HTTP

El primer elemento básico que necesitamos es una interfaz que nos permita leer las credenciales activas y escribir las credenciales pendientes en tu OTBR. Cuando crees un TBR, usa tus propios mecanismos de propiedad, como se muestra aquí con dos ejemplos. La primera opción muestra cómo interactuar con el agente de OTBR de forma local a través de DBUS, mientras que la segunda aprovecha la API de Rest que se puede compilar en el OTBR.

Ninguno de los métodos es seguro y no debe usarse tal como está en un entorno de producción. Sin embargo, un proveedor puede compilar la encriptación en cualquiera de los métodos para usarla en un entorno de producción, o bien puedes extender tu propio servicio de supervisión para emitir llamadas HTTP de bucle invertido o DBUS inherentemente locales.

Opción 1: API de DBUS y HTTP en la secuencia de comandos de Python

91e5fdeed83e9354.png

En este paso, se crea un servicio HTTP básico que expone dos extremos para leer y establecer credenciales y, en última instancia, llamar a los comandos de DBUS.

En la RPi que funcionará como OTBR, instala las dependencias de Python 3:

$ pip install dbus-python shlex json

Ejecuta la secuencia de comandos como:

$  sudo python credentials_server.py 8081
serving at port 8081

En la muestra, se configura un servidor HTTP en el puerto 8081 y se escucha en la ruta de acceso raíz una solicitud GET para recuperar las credenciales de Thread o una solicitud POST para establecerlas. La carga útil siempre es una estructura JSON con el TLV.

La siguiente solicitud PUT establece nuevas credenciales de subproceso pendiente en el OTBR con la ruta de acceso /node/dataset/pending. En este caso, las credenciales pendientes se aplican en 10 segundos:

PUT /node/dataset/pending
Host: <IP>:8081
ContentType: "application/json"
acceptMimeType: "application/json"
...
{
        "ActiveDataset": "<TLV encoded new Thread Dataset>"
"PendingTimestamp": {
        "Seconds": <Unix timestamp in seconds>,
        "Ticks": 0,
        "Authoritative": false
},
"Delay": 10000 // in milliseconds
}

Una solicitud GET a /node/dataset/active recupera las credenciales activas actualmente.

GET /node/dataset/active
Host: <IP>:8081
ContentType = "application/json"
acceptMimeType = "text/plain"
...
<TLV encoded Thread Dataset>

La secuencia de comandos llama a los comandos de lectura y escritura de DBUS a la ruta de acceso del bus io.openthread.BorderRouter.wpan0, la ruta de acceso del objeto /io/openthread/BorderRouter/wpan0:

# D-BUS interface
def call_dbus_method(interface, method_name, *arguments):
    bus = dbus.SystemBus()
    obj = bus.get_object('io.openthread.BorderRouter.wpan0', '/io/openthread/BorderRouter/wpan0')
    iface = dbus.Interface(obj, interface)
    method = getattr(iface, method_name)
    res = method(*arguments)
    return res

def get_dbus_property(property_name):
    return call_dbus_method('org.freedesktop.DBus.Properties', 'Get', 'io.openthread.BorderRouter',
                                 property_name)

def set_dbus_property(property_name, property_value):
    return call_dbus_method('org.freedesktop.DBus.Properties', 'Set', 'io.openthread.BorderRouter',
                                 property_name, property_value)                               

DBUS permite la introspección de sus capacidades. Puedes hacerlo de las siguientes maneras:

$ sudo dbus-send --system --dest=io.openthread.BorderRouter.wpan0 \
        --type=method_call --print-reply /io/openthread/BorderRouter/wpan0 \
        org.freedesktop.DBus.Introspectable.Introspect

También puedes consultar las funciones compatibles documentadas aquí.

Opción 2: API de REST HTTP nativa del agente de OTBR

c748ca5151b6cacb.png

El router de borde OpenThread se compila de forma predeterminada con la marca REST_API=1, que habilita la API de REST. En caso de que la compilación de un codelab anterior no haya habilitado la API de REST, asegúrate de compilar OTBR en tu RPi con esa marca:

$ REST_API=1 INFRA_IF_NAME=wlan0 ./script/setup

Para reiniciar un agente de OTBR, ejecuta lo siguiente:

$ sudo systemctl restart otbr-agent.service

El agente inicia un servidor HTTP en el puerto 8081. Este servidor permite que un usuario o un programa de supervisión realice muchas tareas en el OTBR (documentado aquí). Puedes usar tu navegador, curl o wget para inspeccionar su contenido. Entre las muchas rutas de acceso admitidas, se encuentran los casos de uso descritos anteriormente, con el verbo GET en /node/dataset/active y el verbo PUT en /node/dataset/pending.

3. Cómo configurar Credential Framework en Android

Credenciales preferidas

Los Servicios de Google Play en Android permiten y esperan el registro de credenciales para todos los TBR de tu red. Cada uno se identifica por su ID de agente de router de frontera (BAID). Para realizar esta tarea, usarás el método addCredentials() de la interfaz ThreadNetworkClient. La primera TBR que se agrega al almacenamiento de los Servicios de Google Play determina las credenciales preferidas para este dispositivo móvil.

La app que agrega un conjunto de credenciales de red Thread a su BAID se convierte en el propietario 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. Sin embargo, las credenciales preferidas siempre están disponibles para cualquier app con el consentimiento del usuario. Te recomendamos que mantengas actualizadas las credenciales almacenadas en los Servicios de Google Play cuando se actualice la red del router de borde de Thread. Si bien esa información no se usa en la actualidad, es posible que proporcionemos recorridos mejorados en el futuro.

Incluso si se excluye el primer TBR más adelante, las credenciales preferidas persistirán en el dispositivo Android. Una vez configuradas, otras apps que administren credenciales de Thread pueden obtenerlas de una llamada getPreferredCredentials().

Google TBR Sync

Los dispositivos Android se sincronizan automáticamente con las TBR de Google. Si no hay credenciales en Android, los dispositivos las extraen de los TBR de Google en tu red, y esas credenciales se convierten en las credenciales preferidas. La sincronización entre los TBR y el dispositivo Android solo se produce si el TBR está vinculado a un solo usuario o a dos usuarios que se encuentran en la misma casa inteligente (estructura).

Este proceso también se realizará cuando otro usuario de Google esté en GHA para Android o GHA para iOS y se encuentre en la misma estructura. En el caso de GHA para iOS, las credenciales preferidas se establecen en el almacenamiento de iOS, si no existen credenciales preferidas.

Si hay dos dispositivos Android (o Android + iGHA) en la misma red con diferentes conjuntos de credenciales preferidas, prevalecerá el dispositivo que configuró originalmente la TBR.

Integración de TBR de terceros

Actualmente, la Smart Home del usuario (Structure) no abarca el almacenamiento de la credencial. Cada dispositivo Android tendrá su almacenamiento de BAID, pero una vez que haya un TBR de Google en la red, otros dispositivos Android y dispositivos iOS que ejecuten la app de Google Home para iOS se sincronizarán con ese TBR y tratarán de establecer credenciales locales en el almacenamiento del teléfono.

Antes de que una nueva TBR fuera del alcance cree una red, es importante verificar si ya existe una red preferida en el almacenamiento de Android.

  • Si existe una red preferida, el proveedor debe usarla. Esto garantiza que los dispositivos Thread estén conectados a una sola red Thread siempre que sea posible.
  • Cuando no haya una red preferida, crea un nuevo conjunto de credenciales y asígnale tu TBR en los Servicios de Google Play. Android respetará esas credenciales como las credenciales estándar configuradas en todos los TBR basados en Google, y otros proveedores podrán mejorar el alcance y la solidez de la malla con dispositivos adicionales.

cd8bc726f67b1fa1.png

4. Cómo clonar y modificar tu app para Android

Creamos una app para Android que muestra las principales llamadas posibles a la API de Thread. Puedes usar estos patrones en tu app. En este codelab, clonaremos la app de ejemplo de Google Home para Matter desde GitHub.

Todo el código fuente que se muestra aquí ya está codificado en la app de ejemplo. Te invitamos a modificarlo según tus necesidades, pero también puedes clonar la app o ejecutar los objetos binarios precompilados para inspeccionar la funcionalidad.

  1. Clonarlo con lo siguiente:
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
  1. Descarga y abre Android Studio.
  2. Haz clic en File > Open y selecciona tu repositorio clonado.
  3. Habilita el modo de desarrollador en tu teléfono Android.
  4. Conecta el dispositivo a la computadora con un cable USB.
  5. Ejecuta la app desde Android Studio con <Cmd+R> (OS X) o <Ctrl+R> (Win, Linux).
  6. Navega a la rueda -> Utilidades para desarrolladores -> Red de Thread.
  7. Interactúa con las diferentes opciones disponibles. En las siguientes secciones, analizaremos el código que se ejecuta en cada botón.

¿Existen credenciales preferidas?

La primera pregunta que un fabricante de TBR debe hacerle a Google es si ya existe un conjunto de credenciales preferido en el dispositivo. Este debe ser el punto de partida de tu flujo. El siguiente código consulta el GPS sobre la existencia de credenciales. No solicita el consentimiento del usuario porque no se comparte ninguna credencial.

/**
* Prompts whether credentials exist in storage or not. Consent from user is not necessary
*/

fun doGPSPreferredCredsExist(activity: FragmentActivity) {
 try {
   // Uses the ThreadNetwork interface for the preferred credentials, adding
   // a listener that will receive an intentSenderResult. If that is NULL, 
   // preferred credentials don't exist. If that isn't NULL, they exist.
   // In this case we'll not use it.

   ThreadNetwork.getClient(activity).preferredCredentials.addOnSuccessListener { intentSenderResult ->
     intentSenderResult.intentSender?.let { intentSender ->
       ToastTimber.d("threadClient: preferred credentials exist", activity)
       // don't post the intent on `threadClientIntentSender` as we do when
       // we really want to know which are the credentials. That will prompt a
       // user consent. In this case we just want to know whether they exist
     } ?: ToastTimber.d(
       "threadClient: no preferred credentials found, or no thread module found", activity
     )
   }.addOnFailureListener { e: Exception ->
     Timber.d("ERROR: [${e}]")
   }
 } catch (e: Exception) {
   ToastTimber.e("Error $e", activity)
 }
}

Cómo obtener credenciales preferidas de GPS

En caso de que existan, debes leer las credenciales. La única diferencia con el código anterior es que, después de recibir el intentSenderResult, quieres compilar y lanzar un intent con ese resultado del remitente.

En nuestro código, con fines de organización o arquitectura, usamos un MutableLiveData<IntentSender?> porque el código original está en el ViewModel (ThreadViewModel.kt) y los observadores de intents están en el fragmento de actividad (ThreadFragment.kt). Por lo tanto, una vez que se publique el intentSenderResult en los datos en vivo, ejecutaremos el contenido de este observador:

viewModel.threadClientIntentSender.observe(viewLifecycleOwner) { sender ->
 Timber.d(
   "threadClient: intent observe is called with [${intentSenderToString(sender)}]"
 )
 if (sender != null) {
   Timber.d("threadClient: Launch GPS activity to get ThreadClient")
   threadClientLauncher.launch(IntentSenderRequest.Builder(sender).build())
   viewModel.consumeThreadClientIntentSender()
 }
}

Esto activará el consentimiento del usuario con el uso compartido de credenciales y, si se aprueba, mostrará el contenido a través de las siguientes opciones:

threadClientLauncher =
 registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
   if (result.resultCode == RESULT_OK) {
     val threadNetworkCredentials =
       ThreadNetworkCredentials.fromIntentSenderResultData(result.data!!)
     viewModel.threadPreferredCredentialsOperationalDataset.postValue(
       threadNetworkCredentials
     )
   } else {
     val error = "User denied request."
     Timber.d(error)
     updateThreadInfo(null, "")
   }
 }

A continuación, se describe cómo publicar las credenciales en MutableLiveData<ThreadNetworkCredentials?>.

Cómo configurar las credenciales de GPS

Independientemente de si existen o no, debes registrar tu TBR en los Servicios de Google Play. Tu app será la única que podrá leer las credenciales asociadas con el ID del agente de perímetro de tu TBR, pero si tu TBR es el primero en registrarse, esas credenciales se copiarán en el conjunto de credenciales preferidas. Cualquier app del teléfono puede acceder a esa información, siempre y cuando el usuario lo autorice.

/**
* Last step in setting the GPS thread credentials of a TBR
*/
private fun associateGPSThreadCredentialsToThreadBorderRouterAgent(
 credentials: ThreadNetworkCredentials?,
 activity: FragmentActivity,
 threadBorderAgent: ThreadBorderAgent,
) {
 credentials?.let {
   ThreadNetwork.getClient(activity).addCredentials(threadBorderAgent, credentials)
     .addOnSuccessListener {
       ToastTimber.d("threadClient: Credentials added", activity)
     }.addOnFailureListener { e: Exception ->
       ToastTimber.e("threadClient: Error adding the new credentials: $e", activity)
     }
 }
}

Cómo configurar las credenciales de tu producto de TBR

Esta parte es propiedad de cada proveedor y, en este codelab, la implementamos con el servidor HTTP REST de DBUS+Python o el servidor HTTP REST nativo de OTBR.

/**
* Creates credentials in the format used by the OTBR HTTP server. See its documentation in
* https://github.com/openthread/ot-br-posix/blob/main/src/rest/openapi.yaml#L215
*/
fun createJsonCredentialsObject(newCredentials: ThreadNetworkCredentials): JSONObject {
 val jsonTimestamp = JSONObject()
 jsonTimestamp.put("Seconds", System.currentTimeMillis() / 1000)
 jsonTimestamp.put("Ticks", 0)
 jsonTimestamp.put("Authoritative", false)

 val jsonQuery = JSONObject()
 jsonQuery.put(
   "ActiveDataset",
   BaseEncoding.base16().encode(newCredentials.activeOperationalDataset)
 )
 jsonQuery.put("PendingTimestamp", jsonTimestamp)
 // delay of committing the pending set into active set: 10000ms
 jsonQuery.put("Delay", 10000)

 Timber.d(jsonQuery.toString())

 return jsonQuery
}

//(...)

var response = OtbrHttpClient.createJsonHttpRequest(
 URL("http://$ipAddress:$otbrPort$otbrDatasetPendingEndpoint"),
 activity,
 OtbrHttpClient.Verbs.PUT,
 jsonQuery.toString()
)

Cómo obtener las credenciales de tu producto de TBR

Como se mostró antes, usa el verbo HTTP GET para obtener las credenciales de tu TBR. Consulta la secuencia de comandos de Python de ejemplo.

Compilación e importaciones

Cuando crees tu app para Android, deberás realizar cambios en el manifiesto, la compilación y las importaciones para admitir el módulo de subprocesos de los Servicios de Google Play. En los siguientes tres fragmentos, se resumen la mayoría de las incorporaciones.

Ten en cuenta que nuestra app de ejemplo se creó principalmente para la comisión de Matter. Por lo tanto, sus archivos de manifiesto y Gradle son más complejos que las incorporaciones necesarias para usar solo las credenciales de subproceso.

Cambios en el manifiesto

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    (...)
    <!-- usesCleartextTraffic needed for OTBR local unencrypted communication -->
    <!-- Not needed for Thread Module, only used for HTTP -->
    <uses-feature
    (...)
        android:usesCleartextTraffic="true">

    <application>
    (...)
    <!-- GPS automatically downloads scanner module when app is installed -->
    <!-- Not needed for Thread Module, only used for scanning QR Codes -->
    <meta-data
        android:name="com.google.mlkit.vision.DEPENDENCIES"
        android:value="barcode_ui"/>
    </application>
</manifest>

Build.gradle

// Thread Network
implementation 'com.google.android.gms:play-services-threadnetwork:16.0.0'
// Thread QR Code Scanning
implementation 'com.google.android.gms:play-services-code-scanner:16.0.0'
// Thread QR Code Generation
implementation 'com.journeyapps:zxing-android-embedded:4.1.0'
// Needed for using BaseEncoding class
implementation 'com.google.guava:guava:31.1-jre'

Importaciones relevantes

// Thread Network Module
import com.google.android.gms.threadnetwork.ThreadNetworkCredentials
import com.google.android.gms.threadnetwork.ThreadBorderAgent
import com.google.android.gms.threadnetwork.ThreadNetwork

// Conversion of credentials to/fro Base16 (hex)
import com.google.common.io.BaseEncoding

// HTTP
import java.io.BufferedInputStream
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets

// Co-routines for HTTP calls
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch


// JSON
import org.json.JSONObject

// Logs
import timber.log.Timber

// mDNS/SD
import android.net.nsd.NsdServiceInfo

// QR Code reader / writer
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions
import com.google.mlkit.vision.codescanner.GmsBarcodeScanning
import com.google.zxing.BarcodeFormat
import com.google.zxing.MultiFormatWriter
import com.journeyapps.barcodescanner.BarcodeEncoder

5. Descubrimiento de mDNS/SD

Nuestra app de ejemplo usa el descubrimiento de mDNS/SD para crear una lista de routers de borde de Thread disponibles en la red, así como sus respectivos BAID.

Esto es muy útil cuando ingresas la información de tu TBR en el almacenamiento de la credencial de GPS. Sin embargo, su uso está fuera del alcance de este codelab. Usamos la biblioteca de descubrimiento de servicios de Android NSDManager, y el código fuente completo está disponible en la app de ejemplo, en ServiceDiscovery.kt.

6. Resumen

Una vez que hayas implementado estas llamadas o uses la app de ejemplo, podrás integrar por completo tu OTBR de RPi. Nuestra app de ejemplo expone 8 botones:

91979bf065e9673d.png

Una posible secuencia para integrar tu TBR es la siguiente:

  1. Consulta si existen credenciales preferenciales (en azul, 1ª fila).
  2. Según la respuesta
  3. Obtener credenciales preferidas de GPS (en azul, 2ª fila)
  4. Establece credenciales de TBR en GPS (azul, 3ª fila) -> Selecciona tu TBR -> Crea una aleatoria -> Ingresa el nombre de la red -> Aceptar
  5. Ahora que tienes las credenciales preferidas, configúralas en tu OTBR con Set RPi OTBR credentials, que aplicará esas credenciales al conjunto pendiente.

La configuración predeterminada de la app de ejemplo es usar una demora de 10 segundos. Por lo tanto, después de este período, las credenciales de tu TBR de RPi (y otros nodos que puedan existir en su red) migrarán al nuevo conjunto de datos.

7. Conclusión

En este codelab, clonamos una app para Android de ejemplo y analizamos varios fragmentos de código que usan las APIs de Thread Storage de los Servicios de Google Play. Usamos esas APIs para tener un conjunto de datos común que podamos incorporar en una TBR de RPi, que muestra la TBR de un proveedor.

Tener todos los TBR de un usuario en la misma red mejora la resiliencia y el alcance de la red Thread. También evita recorridos del usuario defectuosos en los que las apps no pueden incorporar dispositivos Thread porque no tienen acceso a las credenciales.

Esperamos que este codelab y las apps de ejemplo te ayuden a diseñar y desarrollar tu propia app y tu producto de router de borde Thread.

8. Referencias

Coprocesador RCP

DBUS