1. Antes de comenzar
En nuestro codelab Thread Border Router (TBR), mostramos cómo compilar un router de borde de Thread basado en una Raspberry Pi. En ese codelab,
- Establece conectividad IP bidireccional entre las redes de Thread y Wi-Fi o Ethernet.
- Proporciona descubrimiento de servicios bidireccional a través de mDNS (en la conexión Wi-Fi o Ethernet) y SRP (en la red Thread).
Este codelab se basa en el anterior y aborda cómo tu router de borde y tu app pueden interactuar con las APIs de Google para crear una sola red de Thread. La convergencia de las 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 establecer 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 Open Thread Border Router (OTBR)
- Placa que proporciona conectividad IEEE 802.15.4 como 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. Configura el servicio HTTP
El primer componente básico que necesitamos es una interfaz que nos permita leer las credenciales activas y escribir las credenciales pendientes en tu OTBR. Cuando compiles un TBR, usa tus propios mecanismos exclusivos, 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 deben usarse tal como están en un entorno de producción. Sin embargo, un proveedor puede crear una encriptación alrededor de cualquiera de los métodos para usarlo en un entorno de producción, o bien puedes extender tu propio servicio de supervisión para emitir llamadas de bucle invertido HTTP o DBUS inherentemente locales.
Opción 1: API de DBUS y HTTP en la secuencia de comandos de Python

En este paso, se crea un servicio HTTP básico que expone dos extremos para leer y configurar credenciales, y, en última instancia, llama a comandos de DBUS.
En la RPi que actuará como tu OTBR, instala las dependencias de Python 3:
$ pip install dbus-python shlex json
Ejecuta la secuencia de comandos de la siguiente manera:
$ sudo python credentials_server.py 8081 serving at port 8081
La muestra configura un servidor HTTP en el puerto 8081 y detecta solicitudes en la ruta raíz, ya sea una solicitud GET para recuperar credenciales de Thread o una solicitud POST para establecer credenciales de Thread. La carga útil siempre es una estructura JSON con el TLV.
La siguiente solicitud PUT establece nuevas credenciales de subproceso pendientes 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 bus io.openthread.BorderRouter.wpan0 y a la ruta de 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 la siguiente manera:
$ 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 capacidades compatibles que se documentan aquí.
Opción 2: API de REST HTTP nativa del agente de OTBR

El Border Router de OpenThread se compila de forma predeterminada con la marca REST_API=1, lo que habilita la API de REST. En caso de que tu 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 OTBR, ejecuta el siguiente comando:
$ 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 realicen muchas tareas en el OTBR (documentadas aquí). Puedes usar tu navegador, curl o wget para inspeccionar su contenido. Entre las muchas rutas 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 el framework de credenciales 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 con su ID de agente de router de borde (BAID). Usarás el método addCredentials() de la interfaz ThreadNetworkClient para realizar esta tarea. El primer 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 de Thread a su BAID se convierte en propietaria de las credenciales y tiene permisos completos para acceder a ellas. Si intentas acceder a las credenciales agregadas por otras apps, recibirás un error de 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 actualmente, 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 que se configuran, otras apps que administran credenciales de Thread pueden obtenerlas a través de una llamada a getPreferredCredentials().
Sincronización de TBR de Google
Los dispositivos Android se sincronizan automáticamente con los TBR de Google. Si no existen 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 si está vinculado 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 la GHA para Android o la GHA para iOS cuando el usuario esté 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 existen dos dispositivos Android (o Android + iGHA) en la misma red con diferentes conjuntos de credenciales preferidas, prevalecerá en el TBR el dispositivo que configuró originalmente el TBR.
Integración de TBR de terceros
Actualmente, el almacenamiento de las credenciales no se limita a la casa inteligente (Structure) del usuario. Cada dispositivo Android tendrá su propio 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 un nuevo TBR externo 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 cuando sea posible.
- Cuando no existe una red preferida, crea un nuevo conjunto de credenciales y asígnalo a tu TBR en los Servicios de Google Play. Android respetará esas credenciales como las credenciales estándar establecidas en todos los TBR basados en Google, y otros proveedores podrán mejorar el alcance y la solidez de tu malla con dispositivos adicionales.

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 archivos binarios prediseñados para inspeccionar la funcionalidad.
- Clónalo con el siguiente comando:
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
- Descarga y abre Android Studio.
- Haz clic en Archivo > Abrir y apunta a tu repositorio clonado.
- Habilita el modo de desarrollador en tu teléfono Android.
- Conéctalo a la computadora con un cable USB.
- Ejecuta la app desde Android Studio con <Cmd+R> (OS X) o <Ctrl+R> (Win, Linux).
- Navega a la rueda -> Utilidades para desarrolladores -> Red de Thread.
- 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 preferido de credenciales 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 las credenciales preferidas de GPS
En caso de que existan, querrás leer las credenciales. La única diferencia con el código anterior es que, después de recibir el intentSenderResult, deseas compilar y lanzar un intent con ese resultado del remitente.
En nuestro código, para fines de organización y 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 activos, 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 para compartir credenciales y, si se aprueba, devolverá el contenido a través de lo siguiente:
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
Ya sea que existan o no, debes registrar tus TBR en los Servicios de Google Play. Tu app será la única que podrá leer las credenciales asociadas al ID del agente de borde de tu TBR, pero, si tu TBR es la primera 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 exclusiva de cada proveedor y, en este codelab, la implementamos con el servidor REST HTTP de DBUS+Python o el servidor REST HTTP 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ó anteriormente, usa el verbo GET de HTTP 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. Los siguientes tres fragmentos resumen la mayoría de las adiciones.
Ten en cuenta que nuestra app de ejemplo se creó principalmente para la puesta en servicio de Matter. Por lo tanto, sus archivos de manifiesto y Gradle son más complejos que las adiciones necesarias para solo usar credenciales de Thread.
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 los 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 credenciales del GPS. Sin embargo, su uso está fuera del alcance de este codelab. Usamos la biblioteca de detección 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 implementes estas llamadas o uses la app de ejemplo, podrás completar la integración de tu OTBR de RPi. Nuestra app de ejemplo expone 8 botones:

Una posible secuencia para incorporar tu TBR es la siguiente:
- Consulta si existen credenciales preferenciales (azul, 1ª fila)
- Según la respuesta
- Get GPS preferred credentials (Obtener credenciales preferidas de GPS; azul, 2ª fila)
- Set TBR credentials in GPS (establece credenciales de TBR en el GPS; azul, fila 3) -> Selecciona tu TBR -> Create Random -> Ingresa el nombre de la red -> Aceptar
- Ahora que tienes las credenciales preferidas, configúralas en tu OTBR con Set RPi OTBR credentials, que aplicará esas credenciales al conjunto pendiente.
La app de ejemplo usa un retraso de 10 segundos de forma predeterminada. 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 podemos incorporar en un TBR de RPi, que muestra el TBR de un proveedor.
Tener todos los TBR de un usuario en la misma red mejora la resistencia y el alcance de la red de 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 de Thread.
8. Referencias
Coprocesador RCP
DBUS