1. Avant de commencer
Dans notre atelier de programmation Thread Border Router (TBR), nous montrons comment créer un routeur de bordure Thread basé sur un Raspberry Pi. Dans cet atelier de programmation, nous avons
- Établissez une connectivité IP bidirectionnelle entre les réseaux Thread et Wi-Fi/Ethernet.
- Fournissez la découverte de services bidirectionnelle via mDNS (sur la liaison Wi-Fi/Ethernet) et SRP (sur le réseau Thread).
Cet atelier de programmation s'appuie sur le précédent et explique comment votre propre routeur de bordure et votre application peuvent interagir avec les API Google pour créer un réseau Thread unique. Il est important de faire converger les identifiants Thread, car cela renforce la robustesse du réseau et simplifie les interactions des utilisateurs avec les applications qui s'appuient sur Thread.
Prérequis
- Suivez l'atelier de programmation OTBR.
- Connaissances de base de Linux, d'Android/Kotlin et du réseau Thread
Points abordés
- Utiliser les API de partage Thread pour obtenir et définir des ensembles d'identifiants
- Configurer votre propre routeur de bordure OpenThread avec les mêmes identifiants que le réseau Google
Prérequis
- Carte Raspberry Pi 4 ou autre carte basée sur Linux exécutant Open Thread Border Router (OTBR)
- Carte qui fournit une connectivité IEEE 802.15.4 en tant que coprocesseur radio (RCP). Consultez la liste des dépôts de différents fournisseurs de SoC et leurs instructions sur la page GitHub OpenThread.
2. Configurer le service HTTP
Le premier bloc de construction dont nous avons besoin est une interface qui nous permet de lire les identifiants actifs et d'écrire les identifiants en attente dans votre OTBR. Lorsque vous créez un TBR, utilisez vos propres mécanismes propriétaires, comme illustré ici avec deux exemples. La première option montre comment interagir avec l'agent OTBR en local via DBUS, tandis que la seconde utilise l'API REST qui peut être créée sur l'OTBR.
Aucune de ces méthodes n'est sécurisée et ne doit être utilisée telle quelle dans un environnement de production. Toutefois, un fournisseur peut créer un chiffrement autour de l'une ou l'autre de ces méthodes pour l'utiliser dans un environnement de production. Vous pouvez également étendre votre propre service de surveillance pour émettre des appels HTTP en boucle fermée ou des appels DBUS intrinsèquement locaux.
Option 1 : API DBUS et HTTP sur le script Python

Cette étape crée un service HTTP minimaliste qui expose deux points de terminaison pour lire et définir les identifiants, en appelant finalement les commandes DBUS.
Sur le RPi qui servira d'OTBR, installez les dépendances Python 3 :
$ pip install dbus-python shlex json
Exécutez le script en tant que :
$ sudo python credentials_server.py 8081 serving at port 8081
L'exemple configure un serveur HTTP sur le port 8081 et écoute le chemin racine pour une requête GET permettant de récupérer les identifiants Thread ou une requête POST permettant de définir les identifiants Thread. La charge utile est toujours une structure JSON avec le TLV.
La requête PUT suivante définit de nouveaux identifiants Thread en attente sur l'OTBR à l'aide du chemin d'accès /node/dataset/pending. Dans ce cas, les identifiants en attente sont appliqués en 10 secondes :
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
}
Une requête GET à /node/dataset/active récupère les identifiants actifs.
GET /node/dataset/active Host: <IP>:8081 ContentType = "application/json" acceptMimeType = "text/plain" ... <TLV encoded Thread Dataset>
Le script appelle les commandes DBUS R/W au chemin de bus io.openthread.BorderRouter.wpan0, chemin d'objet /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 permet l'introspection de ses capacités. Vous pouvez procéder comme suit :
$ sudo dbus-send --system --dest=io.openthread.BorderRouter.wpan0 \
--type=method_call --print-reply /io/openthread/BorderRouter/wpan0 \
org.freedesktop.DBus.Introspectable.Introspect
Vous pouvez également consulter les fonctionnalités compatibles décrites sur cette page.
Option 2 : API REST HTTP native de l'agent OTBR

Le routeur de bordure OpenThread est compilé par défaut avec l'indicateur REST_API=1, ce qui active l'API REST. Si votre compilation à partir d'un atelier de programmation précédent n'a pas activé l'API REST, assurez-vous de compiler OTBR sur votre RPi avec cet indicateur :
$ REST_API=1 INFRA_IF_NAME=wlan0 ./script/setup
Pour redémarrer un agent OTBR, exécutez la commande suivante :
$ sudo systemctl restart otbr-agent.service
L'agent démarre un serveur HTTP sur le port 8081. Ce serveur permet à un utilisateur ou à un programme de surveillance d'effectuer de nombreuses tâches dans OTBR (documentées ici). Vous pouvez utiliser votre navigateur, curl ou wget pour inspecter son contenu. Parmi les nombreux chemins d'accès compatibles figurent les cas d'utilisation décrits ci-dessus, avec le verbe GET sur /node/dataset/active et le verbe PUT sur /node/dataset/pending.
3. Configurer le framework d'identifiants sur Android
Identifiants préférés
Les services Google Play sur Android autorisent et attendent l'enregistrement des identifiants pour tous les TBR de votre réseau. Chacun est identifié par son ID d'agent de routeur de bordure (BAID). Pour ce faire, vous utiliserez la méthode addCredentials() de l'interface ThreadNetworkClient. Le premier TBR ajouté au stockage des services Google Play détermine les identifiants préférés pour cet appareil mobile.
L'application qui ajoute un ensemble d'identifiants réseau Thread à son BAID devient propriétaire des identifiants et dispose de toutes les autorisations pour y accéder. Si vous essayez d'accéder à des identifiants ajoutés par d'autres applications, vous recevrez une erreur PERMISSION_DENIED. Toutefois, les identifiants préférés sont toujours disponibles pour n'importe quelle application avec l'autorisation de l'utilisateur. Nous vous recommandons de mettre à jour les identifiants stockés dans les services Google Play lorsque le réseau Thread Border Router est mis à jour. Bien que ces informations ne soient pas utilisées aujourd'hui, nous pourrons proposer des parcours améliorés à l'avenir.
Même si la première TBR est exclue ultérieurement, les identifiants préférés resteront sur l'appareil Android. Une fois définies, les autres applications qui gèrent les identifiants Thread peuvent les obtenir à partir d'un appel getPreferredCredentials().
Synchronisation Google TBR
Les appareils Android se synchronisent automatiquement avec les TBR Google. Si aucun identifiant n'existe sur Android, les appareils les extraient des TBR Google de votre réseau, et ces identifiants deviennent les identifiants préférés. La synchronisation entre les TBR et l'appareil Android ne se produit que si le TBR est associé à un seul utilisateur ou à deux utilisateurs qui se trouvent dans la même maison connectée (structure).
Ce processus se produit également lorsqu'un autre utilisateur Google se trouve sur GHA pour Android ou GHA pour iOS dans la même structure. Dans le cas de GHA pour iOS, les identifiants préférés sont définis sur le stockage iOS s'il n'en existe aucun.
Si deux appareils Android (ou Android + iGHA) existent sur le même réseau avec des ensembles d'identifiants préférés différents, l'appareil qui a configuré à l'origine le TBR prévaudra sur le TBR.
Intégration de TBR tiers
Le stockage des identifiants n'est actuellement pas limité à la maison connectée (Structure) de l'utilisateur. Chaque appareil Android disposera de son propre espace de stockage BAID, mais une fois qu'un TBR Google est présent sur le réseau, les autres appareils Android et les appareils iOS exécutant l'application Google Home pour iOS se synchronisent avec ce TBR et tentent de définir des identifiants locaux dans l'espace de stockage du téléphone.
Avant qu'un nouveau TBR OOB ne crée un réseau, il est important de vérifier si un réseau préféré existe déjà dans le stockage d'Android.
- Si un réseau préféré existe, le fournisseur doit l'utiliser. Cela permet de s'assurer que les appareils Thread sont connectés à un seul réseau Thread, lorsque cela est possible.
- Lorsqu'aucun réseau préféré n'existe, créez un ensemble d'identifiants et attribuez-le à votre TBR dans les services Google Play. Android respectera ces identifiants en tant qu'identifiants standards définis sur tous les TBR basés sur Google, et d'autres fournisseurs pourront améliorer la couverture et la robustesse de votre réseau maillé avec des appareils supplémentaires.

4. Cloner et modifier votre application Android
Nous avons créé une application Android qui présente les principaux appels possibles à l'API Thread. Vous pouvez utiliser ces modèles dans votre application. Dans cet atelier de programmation, nous allons cloner l'application Google Home Sample App for Matter depuis GitHub.
Tout le code source présenté ici est déjà codé dans l'application exemple. Vous êtes invité à le modifier selon vos besoins, mais vous pouvez simplement cloner l'application ou exécuter les binaires précompilés pour inspecter la fonctionnalité.
- Clonez-le à l'aide de la commande suivante :
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
- Téléchargez et ouvrez Android Studio.
- Cliquez sur Fichier > Ouvrir, puis pointez sur le dépôt cloné.
- Activez le mode développeur sur votre téléphone Android.
- Connectez-le à votre ordinateur à l'aide d'un câble USB.
- Exécutez l'application depuis Android Studio en appuyant sur <Cmd+R> (OS X) ou <Ctrl+R> (Windows, Linux).
- Accédez à la roue > Utilitaires pour les développeurs > Réseau Thread.
- Interagissez avec les différentes options disponibles. Dans les sections ci-dessous, nous allons décortiquer le code exécuté sur chaque bouton.
Existe-t-il des identifiants privilégiés ?
La première question qu'un fabricant de TBR doit poser à Google est de savoir si un ensemble d'identifiants privilégiés existe déjà dans l'appareil. Il s'agit du point de départ de votre flux. Le code ci-dessous interroge GPS sur l'existence des identifiants. Aucune invite de consentement de l'utilisateur n'est affichée, car aucune identité n'est partagée.
/**
* 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)
}
}
Obtenir les identifiants GPS préférés
Si elles existent, vous devez lire les identifiants. La seule différence par rapport au code précédent est qu'après avoir reçu le intentSenderResult, vous souhaitez créer et lancer un intent à l'aide de ce résultat de l'expéditeur.
Dans notre code, nous utilisons un MutableLiveData<IntentSender?> à des fins d'organisation/d'architecture, car le code d'origine se trouve dans le ViewModel (ThreadViewModel.kt) et les observateurs d'intent se trouvent dans le fragment d'activité (ThreadFragment.kt). Ainsi, une fois que intentSenderResult est publié dans les données en direct, nous exécutons le contenu de cet observateur :
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()
}
}
Cela déclenchera le consentement de l'utilisateur au partage des identifiants et, si celui-ci est approuvé, renverra le contenu via :
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, "")
}
}
La procédure pour publier les identifiants sur MutableLiveData<ThreadNetworkCredentials?> est décrite ci-dessous.
Définir les identifiants GPS
Que vos TBR existent ou non, vous devez les enregistrer dans les services Google Play. Votre application sera la seule à pouvoir lire les identifiants associés à l'ID d'agent frontalier de votre TBR, mais si votre TBR est le premier à s'enregistrer, ces identifiants seront copiés dans l'ensemble des identifiants préférés. Ces informations sont accessibles à n'importe quelle application sur le téléphone, à condition que l'utilisateur l'y autorise.
/**
* 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)
}
}
}
Définir les identifiants pour votre produit TBR
Cette partie est propre à chaque fournisseur. Dans cet atelier de programmation, nous l'implémentons à l'aide du serveur REST HTTP DBUS+Python ou du serveur REST HTTP natif d'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()
)
Obtenir les identifiants de votre produit TBR
Comme indiqué précédemment, utilisez le verbe HTTP GET pour obtenir les identifiants de votre TBR. Consultez l'exemple de script Python.
Créer et importer
Lorsque vous créez votre application Android, vous devez modifier votre fichier manifeste, votre compilation et vos importations pour prendre en charge le module de thread des services Google Play. Les trois extraits suivants résument la plupart des ajouts.
Notez que notre application exemple est principalement conçue pour la mise en service Matter. Par conséquent, ses fichiers Manifest et Gradle sont plus complexes que les ajouts nécessaires pour utiliser uniquement les identifiants Thread.
Modifications du fichier manifeste
<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'
Importations pertinentes
// 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. Découverte mDNS/SD
Notre application exemple utilise la découverte mDNS/SD pour créer une liste des routeurs de bordure Thread disponibles sur le réseau, ainsi que leurs BAID respectifs.
Cela est très utile lorsque vous saisissez les informations de votre TBR dans le stockage des identifiants GPS. Toutefois, son utilisation dépasse le cadre de cet atelier de programmation. Nous utilisons la bibliothèque Android Service Discovery NSDManager. Le code source complet est disponible dans l'application exemple, dans ServiceDiscovery.kt.
6. Synthèse
Une fois que vous avez implémenté ces appels ou que vous utilisez l'application exemple, vous pouvez intégrer complètement votre OTBR RPi. Notre application exemple comporte huit boutons :

Voici une séquence possible pour intégrer votre TBR :
- Requête pour savoir si des identifiants préférentiels existent (bleu, 1re ligne)
- Selon la réponse
- Obtenir les identifiants GPS préférés (bleu, deuxième ligne)
- Définir les identifiants TBR dans GPS (bleu, 3e ligne) > Sélectionnez votre TBR > Créer un identifiant aléatoire > Saisissez le nom du réseau > OK
- Maintenant que vous avez des identifiants préférés, définissez-les sur votre OTBR à l'aide de Set RPi OTBR credentials (Définir les identifiants OTBR RPi), qui appliquera ces identifiants à l'ensemble en attente.
Par défaut, l'application exemple utilise un délai de 10 secondes. Par conséquent, après cette période, les identifiants de votre RPi TBR (et des autres nœuds pouvant exister sur son réseau) migreront vers le nouvel ensemble de données.
7. Conclusion
Dans cet atelier de programmation, nous avons cloné un exemple d'application Android et analysé plusieurs extraits de code qui utilisent les API Thread Storage des services Google Play. Nous avons utilisé ces API pour disposer d'un ensemble de données commun que nous pouvons intégrer à un TBR RPi, qui présente le TBR d'un fournisseur.
Le fait que tous les TBR d'un utilisateur se trouvent sur le même réseau améliore la résilience et la couverture du réseau Thread. Cela empêche également les parcours utilisateur défectueux où les applications ne peuvent pas intégrer les appareils Thread, car elles n'ont pas accès aux identifiants.
Nous espérons que cet atelier de programmation et les exemples d'applications vous aideront à concevoir et à développer votre propre application et votre produit Thread Border Router.
8. Références
Coprocesseur RCP
DBUS