Créer un routeur de bord Android

Afficher le code source sur Android Code Search

Si vous n'êtes pas un fournisseur d'appareils Android ou de puces Thread, vous pouvez arrêter de lire cet article.

Ce document vous explique comment créer un nouveau routeur de bord Thread basé sur Android avec le dernier code source AOSP. En suivant ce document, vous allez apprendre:

  1. l'architecture globale et l'état de la prise en charge de Thread dans Android
  2. créer votre propre service HAL Thread ;
  3. rendre votre appareil compatible avec Google Home
  4. comment tester votre routeur de bordure Thread

Si vous avez besoin d'aide, signalez un problème sur GitHub ou ouvrez une discussion si vous avez des questions.

Présentation

La pile de threads Android est basée sur OpenThread et ot-br-posix, qui sont publiés en Open Source par Google sur GitHub. De la même manière qu'OpenThread est développé dans un dépôt GitHub public, la pile Android Thread est développée dans le code source AOSP public. Toutes les fonctionnalités et corrections de bugs sont d'abord envoyées dans AOSP. Cela permet aux fournisseurs de commencer à adopter les dernières versions de Thread sans attendre les versions Android régulières.

Architecture

L'ensemble de la pile de threads Android se compose de deux composants principaux: la pile de threads principale dans une partition système générique et le service HAL de thread dans une partition du fournisseur. Les fournisseurs d'appareils ne doivent généralement s'occuper que de créer le service HAL.

android-thread-arch

Voici un bref résumé du fonctionnement de la pile Thread Android : - Un service système Thread Java est présent dans le serveur système, qui gère l'ensemble de la pile. Il fournit l'API système Thread, crée l'interface de tunnel thread-wpan, enregistre le réseau Thread auprès du service de connectivité et implémente les fonctionnalités de routage de bordure et de proxy publicitaire. - La pile Thread / OpenThread principale est hébergée dans un processus natif autonome non privilégié nommé ot-daemon. ot-daemon est géré directement par le service système Java via des API AIDL privées et accède à la radio matérielle Thread via l'API Thread HAL. - Un service HAL Thread fourni par le fournisseur DOIT implémenter l'API HAL Thread. Il fonctionne généralement en tant que RCP et implémente le protocole spinel.

Où se trouve le code ?

Configurer l'environnement de développement

Les fournisseurs d'appareils Android qui ont déjà établi un environnement de développement Android pour l'appareil peuvent ignorer cette section.

Si vous êtes nouveau dans l'écosystème Android ou que vous êtes un fournisseur de silicium qui souhaite rendre votre puce Thread compatible avec Android et fournir une assistance aux fournisseurs d'appareils, lisez la suite.

Suivez l'atelier de programmation pour les développeurs Android

Pour configurer votre environnement de développement Android pour la première fois, utilisez l'atelier de programmation suivant: https://source.android.com/docs/setup/start. À la fin de cet atelier de programmation, vous pourrez créer et exécuter un appareil Cuttlefish simulé à partir du code source.

Créer votre service HAL Thread

Essayer Thread dans Cuttlefish

Cuttlefish est l'appareil Android virtuel. Avant de commencer à créer votre propre service HAL, il est préférable d'essayer Thread dans Cuttlefish pour comprendre le fonctionnement de HAL.

Un service HAL Thread par défaut est fourni dans Cuttlefish et est implémenté avec le RCP simulé, qui transceiche des paquets via un socket UDP vers et depuis une radio Thread (802.15.4) simulée.

Dans l'instance Cuttlefish, une application "ThreadNetworkDemoApp" est préinstallée. Ouvrez cette application pour rejoindre l'appareil Cuttlefish à un réseau Thread par défaut.

demoapp-screenshot

Les outils de ligne de commande ot-ctl et ot-cli-ftd sont également fournis pour configurer votre réseau Thread à des fins de test. Ces outils sont compatibles avec toutes les commandes de ligne de commande OpenThread que vous connaissez peut-être déjà.

Vous pouvez rechercher des journaux du service HAL de thread Cuttlefish en procédant comme suit:

adb logcat | egrep -i threadnetwork-service

07-21 10:43:05.048     0     0 I init    : Parsing file /apex/com.android.hardware.threadnetwork/etc/threadnetwork-service.rc...
07-21 10:59:27.233   580   580 W android.hardware.threadnetwork-service: ThreadChip binder is unlinked
07-21 10:59:27.233   580   580 I android.hardware.threadnetwork-service: Close IThreadChip successfully
07-21 10:59:27.385   580   580 I android.hardware.threadnetwork-service: Open IThreadChip successfully

Vous pouvez également rechercher les journaux du daemon ot en effectuant les opérations suivantes:

adb logcat | egrep -i ot-daemon
07-21 10:43:48.741     0     0 I init    : starting service 'ot-daemon'...
07-21 10:43:48.742     0     0 I init    : Created socket '/dev/socket/ot-daemon/thread-wpan.sock', mode 660, user 1084, group 1084
07-21 10:43:48.762     0     0 I init    : ... started service 'ot-daemon' has pid 2473
07-21 10:46:26.320  2473  2473 I ot-daemon: [I] P-Daemon------: Session socket is ready
07-21 10:46:30.290  2473  2473 W ot-daemon: [W] P-Daemon------: Daemon read: Connection reset by peer
07-21 10:48:07.264  2473  2473 I ot-daemon: [INFO]-BINDER--: Start joining...
07-21 10:48:07.267  2473  2473 I ot-daemon: [I] Settings------: Saved ActiveDataset
07-21 10:48:07.267  2473  2473 I ot-daemon: [I] DatasetManager: Active dataset set
07-21 10:48:07.273  2473  2473 I ot-daemon: [I] DnssdServer---: Started
07-21 10:48:07.273  2473  2473 I ot-daemon: [N] Mle-----------: Role disabled -> detached
07-21 10:48:07.273  2473  2473 I ot-daemon: [I] Mle-----------: AttachState Idle -> Start
07-21 10:48:07.273  2473  2473 I ot-daemon: [I] Notifier------: StateChanged (0x111fd11d) [Ip6+ Role LLAddr MLAddr KeySeqCntr Ip6Mult+ Channel PanId NetName ExtPanId ...
07-21 10:48:07.273  2473  2473 I ot-daemon: [I] Notifier------: StateChanged (0x111fd11d) ... NetworkKey PSKc SecPolicy NetifState ActDset]

Le service HAL Thread de Cuttlefish utilise le service HAL Thread par défaut ainsi que le binaire RCP simulé OpenThread. Pour en savoir plus, consultez la section suivante.

Service HAL par défaut

Un service HAL par défaut est inclus avec l'API HAL Thread. Le service HAL par défaut est compatible avec les appareils RCP simulés et réels. Il reçoit une URL d'appareil RCP facultative. Si l'URL n'est pas fournie, l'appareil RCP simulé est utilisé par défaut.

Dans le fichier hardware/interfaces/threadnetwork/aidl/default/threadnetwork-service.rc:

service vendor.threadnetwork_hal /apex/com.android.hardware.threadnetwork/bin/hw/android.hardware.threadnetwork-service
    class hal
    user thread_network

Cela équivaut à:

service vendor.threadnetwork_hal /apex/com.android.hardware.threadnetwork/bin/hw/android.hardware.threadnetwork-service spinel+hdlc+forkpty:///apex/com.android.hardware.threadnetwork/bin/ot-rcp?forkpty-arg=1
    class hal
    user thread_network

Pour les appareils RCP réels, il est compatible avec les interfaces SPI et UART. Vous pouvez spécifier l'appareil avec les schémas spinel+spi://, spinel+hdlc+uart:// et spinel+socket://, respectivement.

Comprendre l'APEX du fournisseur

Comme la pile Thread dans le module principal de partage de connexion, le service HAL Thread par défaut de Cuttlefish est également empaqueté dans un module APEX. Toutefois, il s'agit d'un module APEX du fournisseur qui sera installé dans /vendor/apex/ (les artefacts du module seront décompressés dans /apex/com.android.hardware.threadnetwork/).

apex {
    name: "com.android.hardware.threadnetwork",
    manifest: "manifest.json",
    file_contexts: "file_contexts",
    key: "com.android.hardware.key",
    certificate: ":com.android.hardware.certificate",
    updatable: false,
    vendor: true,

    binaries: [
        "android.hardware.threadnetwork-service",
        "ot-rcp",
    ],

    prebuilts: [
        "threadnetwork-default.xml", // vintf_fragment
        "threadnetwork-service.rc", // init_rc
        "android.hardware.thread_network.prebuilt.xml", // permission
    ],
}

Vous devrez prêter attention à certaines configurations importantes ou apporter des modifications lorsque vous créerez votre propre module HAL APEX:

  • file_contexts: décrit les fichiers binaires / de données fournis dans ce module APEX ou les fichiers auxquels le service HAL doit accéder (par exemple, l'appareil RCP). Cela vous permet de spécifier des règles sepolicy spécifiques pour que votre service HAL puisse accéder à l'appareil RCP matériel.

  • binaries: fichier binaire fourni dans ce module APEX

  • threadnetwork-service.rc: mode de démarrage du service HAL. Vous devez spécifier le chemin d'accès de l'appareil RCP ici.

  • android.hardware.thread_network.prebuilt.xml: définit la fonctionnalité matérielle android.hardware.thread_network. Cela permet au système Android de savoir que votre appareil est compatible avec le matériel Thread. Sinon, la pile de threads Android ne sera pas activée.

Créer votre service HAL

Que vous soyez développeur d'appareils Android ou fournisseur de silicium, vous devez savoir créer un micrologiciel OT RCP pour votre puce Thread. Les instructions suivantes supposent que la puce matérielle est correctement câblée et validée.

Le moyen le plus simple de compiler votre APEX HAL consiste à créer un nouvel APEX avec les binaires et les précompilés de l'APEX HAL par défaut. Par exemple, si votre entreprise est Banana et que l'appareil RCP de votre appareil est /dev/ttyACM0, votre APEX HAL Thread se présente comme suit:

  • Android.bp :
  prebuilt_etc {
    name: "banana-threadnetwork-service.rc",
    src: "banana-threadnetwork-service.rc",
    installable: false,
  }

  apex {
    name: "com.banana.android.hardware.threadnetwork",
    manifest: "manifest.json",
    file_contexts: "file_contexts",
    key: "com.android.hardware.key",
    certificate: ":com.android.hardware.certificate",
    updatable: false,
    vendor: true,

    binaries: [
        "android.hardware.threadnetwork-service",
    ],

    prebuilts: [
        "banana-threadnetwork-service.rc",
        "threadnetwork-default.xml",
        "android.hardware.thread_network.prebuilt.xml",
    ],
  }
  • file_contexts :
  (/.*)?                                                      u:object_r:vendor_file:s0
  /etc(/.*)?                                                  u:object_r:vendor_configs_file:s0
  /bin/hw/android\.hardware\.threadnetwork-service            u:object_r:hal_threadnetwork_default_exec:s0
  /dev/ttyACM0                                                u:object_r:threadnetwork_rcp_device:s0

Les chemins d'accès des fichiers de la première colonne sont liés à /apex/com.android.hardware.threadnetwork/.

  • threadnetwork-service.rc :
  service vendor.threadnetwork_hal /apex/com.android.hardware.threadnetwork/bin/hw/android.hardware.threadnetwork-service spinel+hdlc+uart:///dev/ttyACM0?uart-baudrate=115200
    class hal
    user root
  • manifest.json :
  {
    "name": "com.android.hardware.threadnetwork",
    "version": 1
  }

En supposant que vous créez un appareil nommé Orange, le répertoire de configuration spécifique à l'appareil se présente comme suit:

device/banana/orange/threadnetwork/
    sepolicy/
    Android.bp
    file_contexts
    manifest.json
    threadnetwork-default.xml
    threadnetwork-service.rc

Consultez la section suivante pour connaître les règles sepolicy à ajouter dans le sous-répertoire sepolicy/.

Règles Sepolicy pour l'appareil RCP

Par défaut, votre service HAL Thread n'a pas accès à l'appareil RCP (par exemple, /dev/ttyACM0). Des règles sepolicy personnalisées doivent être ajoutées au répertoire sepolicy/.

Créez un fichier sepolicy/threadnetwork_hal.te avec le contenu suivant:

type threadnetwork_rcp_device, dev_type;

# Allows the Thread HAL service to read / write the Thread RCP device
allow hal_threadnetwork_default threadnetwork_rcp_device:chr_file rw_file_perms;

Réunir

Vous avez maintenant terminé presque tous les besoins de code pour ajouter Thread. La dernière étape consiste à ajouter les règles Thread HAL APEX et sepolicy à l'image de votre appareil.

Pour ce faire, ajoutez le code ci-dessous à la Makefile de votre appareil (par exemple, device.mk):

PRODUCT_PACKAGES += com.banana.hardware.threadnetwork
BOARD_SEPOLICY_DIRS += device/banana/orange/threadnetwork/sepolicy

Si tout fonctionne, vous devriez maintenant voir le journal du service HAL de thread semblable à celui-ci:

adb logcat | egrep -i threadnetwork-service
08-13 13:26:41.751   477   477 I android.hardware.threadnetwork-service: ServiceName: android.hardware.threadnetwork.IThreadChip/chip0, Url: spinel+spi
08-13 13:26:41.751   477   477 I android.hardware.threadnetwork-service: Thread Network HAL is running
08-13 13:26:55.165   477   477 I android.hardware.threadnetwork-service: Open IThreadChip successfully

Le journal ot-daemon se présente comme suit:

adb logcat -s ot-daemon
08-13 13:26:55.157  1019  1019 I ot-daemon: [NOTE]-AGENT---: Running OTBR_AGENT/Unknown
08-13 13:26:55.157  1019  1019 I ot-daemon: [NOTE]-AGENT---: Thread version: 1.3.0
08-13 13:26:55.157  1019  1019 I ot-daemon: [NOTE]-AGENT---: Thread interface: thread-wpan
08-13 13:26:55.157  1019  1019 I ot-daemon: [NOTE]-AGENT---: Backbone interface is not specified
08-13 13:26:55.157  1019  1019 I ot-daemon: [NOTE]-AGENT---: Radio URL: threadnetwork_hal://binder?none
08-13 13:26:55.157  1019  1019 I ot-daemon: [NOTE]-ILS-----: Infra link selected:
08-13 13:26:55.160  1019  1019 I ot-daemon: [I] Platform------: [HAL] Wait for getting the service android.hardware.threadnetwork.IThreadChip/chip0 ...
08-13 13:26:55.165  1019  1019 I ot-daemon: [I] Platform------: [HAL] Successfully got the service android.hardware.threadnetwork.IThreadChip/chip0
08-13 13:26:55.275  1019  1019 I ot-daemon: [I] P-RadioSpinel-: RCP reset: RESET_UNKNOWN
08-13 13:26:55.276  1019  1019 I ot-daemon: [I] P-RadioSpinel-: Software reset RCP successfully
08-13 13:26:55.277  1019  1019 I ot-daemon: [I] P-RadioSpinel-: RCP reset: RESET_POWER_ON
08-13 13:26:55.322  1019  1019 I ot-daemon: [I] ChildSupervsn-: Timeout: 0 -> 190
08-13 13:26:55.324  1019  1019 I ot-daemon: [I] RoutingManager: Initializing - InfraIfIndex:0
08-13 13:26:55.324  1019  1019 I ot-daemon: [I] InfraIf-------: Init infra netif 0
08-13 13:26:55.324  1019  1019 I ot-daemon: [I] Settings------: Read BrUlaPrefix fd7b:cc45:ff06::/48
08-13 13:26:55.324  1019  1019 I ot-daemon: [N] RoutingManager: BR ULA prefix: fd7b:cc45:ff06::/48 (loaded)
08-13 13:26:55.324  1019  1019 I ot-daemon: [I] RoutingManager: Generated local OMR prefix: fd7b:cc45:ff06:1::/64
08-13 13:26:55.324  1019  1019 I ot-daemon: [N] RoutingManager: Local on-link prefix: fdde:ad00:beef:cafe::/64
08-13 13:26:55.324  1019  1019 I ot-daemon: [I] RoutingManager: Enabling

Personnalisation

Le module principal Thread (qui fait partie du module "Tethering") fournit quelques configurations superposables que les fournisseurs peuvent spécifier pour personnaliser le comportement de la pile. Pour obtenir la liste complète, consultez config_thread.xml.

En règle générale, vous devez définir config_thread_border_router_default_enabled sur true pour activer votre appareil en tant que routeur de bordure Thread, et remplacer les valeurs config_thread_vendor_name, config_thread_vendor_oui et config_thread_model_name par celles de votre fournisseur ou de votre produit. Ces valeurs seront incluses dans le service mDNS _meshcop._udp, qui est toujours annoncé par un routeur de bordure Thread.

Pour ajouter la superposition, vous devez créer une cible ConnectivityOverlayOrange runtime_resource_overlay pour votre appareil Orange. Créez un répertoire ConnectivityOverlay/ sous device/banana/orange/rro_overlays et créez-y le contenu suivant:

device/banana/orange/rro_overlays/ConnectivityOverlay/
  res
    values
      config_thread.xml
  Android.bp
  AndroidManifest.xml
  • Android.bp :
  package {
      default_applicable_licenses: ["Android-Apache-2.0"],
  }

  runtime_resource_overlay {
      name: "ConnectivityOverlayOrange",
      manifest: "AndroidManifest.xml",
      resource_dirs: ["res"],
      certificate: "platform",
      product_specific: true,
      sdk_version: "current",
  }
  • AndroidManifest.xml :
  <!-- Orange overlays for the Connectivity module -->
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.banana.android.connectivity.resources.orange"
      android:versionCode="1"
      android:versionName="1.0">
      <application android:hasCode="false" />

      <!-- If your device uses google-signed mainline modules, the targetPackage
      needs to be "com.google.android.connectivity.resources", otherise, it
      should be "com.android.connectivity.resources"
      -->
      <overlay
          android:targetPackage="com.google.android.connectivity.resources"
          android:targetName="ServiceConnectivityResourcesConfig"
          android:isStatic="true"
          android:priority="1"/>
  </manifest>
  
  • config_thread.xml :
  <bool name="config_thread_border_router_default_enabled">true</bool>
  <string translatable="false" name="config_thread_vendor_name">Banana Inc.</string>
  <string translatable="false" name="config_thread_vendor_oui">AC:DE:48</string>
  <string translatable="false" name="config_thread_model_name">Orange</string>
  

Comme pour l'APEX HAL, vous devez ajouter l'application en superposition à votre fichier device.mk:

PRODUCT_PACKAGES += \
    ConnectivityOverlayOrange</code>

Si tout fonctionne, vous verrez que ot-daemon consigne le nom du fournisseur et du modèle au tout début du journal:

adb logcat -s ot-daemon
07-22 15:31:37.693  1472  1472 I ot-daemon: [I] P-Daemon------: Session socket is ready
07-22 15:31:37.693  1472  1472 I ot-daemon: [I] Cli-----------: Input: state
07-22 15:31:37.693  1472  1472 I ot-daemon: [I] Cli-----------: Output: disabled
07-22 15:31:37.693  1472  1472 I ot-daemon: [I] Cli-----------: Output: Done
07-22 15:31:37.693  1472  1472 W ot-daemon: [W] P-Daemon------: Daemon read: Connection reset by peer
07-22 15:31:50.091  1472  1472 I ot-daemon: [I] P-Daemon------: Session socket is ready
07-22 15:31:50.091  1472  1472 I ot-daemon: [I] Cli-----------: Input: factoryreset
07-22 15:31:50.092  1472  1472 I ot-daemon: [I] Settings------: Wiped all info
07-22 15:31:50.092  1472  1472 I ot-daemon: [INFO]-ADPROXY-: Stopped
07-22 15:31:50.092  1472  1472 I ot-daemon: [INFO]-DPROXY--: Stopped
07-22 15:31:50.092  1472  1472 I ot-daemon: [INFO]-BA------: Stop Thread Border Agent
07-22 15:31:50.092  1472  1472 I ot-daemon: [INFO]-BA------: Unpublish meshcop service Banana Inc. Orange #4833._meshcop._udp.local
07-22 15:31:50.092  1472  1472 I ot-daemon: [INFO]-MDNS----: Removing service Banana Inc. Orange #4833._meshcop._udp
07-22 15:31:50.092  1472  1472 I ot-daemon: [INFO]-MDNS----: Unpublishing service Banana Inc. Orange #4833._meshcop._udp listener ID = 0

être compatible avec Google Home ;

En outre, si vous souhaitez que votre routeur de bordure soit utilisé par l'écosystème Google Home, vous pouvez spécifier cette configuration dans config_thread.xml:

<string-array name="config_thread_mdns_vendor_specific_txts">
  <item>vgh=1</item>
</string-array>

Tests

Votre appareil devrait désormais être compatible avec la spécification du routeur de bordure Thread 1.3 ou version ultérieure. Avant de l'envoyer au programme de certification Thread, vous devez effectuer quelques tests xTS Android pour vous assurer de sa compatibilité.

  • Le test VTS vérifie que le service HAL Thread fonctionne comme prévu sur votre appareil. Vous pouvez exécuter les tests avec la commande

    atest VtsHalThreadNetworkTargetTest

  • Le test CTS permet de s'assurer que les API Thread fonctionnent comme prévu sur votre appareil. Vous pouvez exécuter les tests avec la commande

    atest CtsThreadNetworkTestCases

  • Le test d'intégration offre une garantie de qualité plus élevée du fonctionnement du code principal Thread sur votre appareil. Vous pouvez exécuter les tests avec la commande

    atest ThreadNetworkIntegrationTests

Vous trouverez également d'autres instructions pour exécuter des tests VTS/CTS/MTS avec ces suites de tests publiées:

Tester avec l'application de démonstration Thread

Comme pour l'appareil Cuttlefish, vous pouvez ajouter l'application de démonstration Thread à votre image système:

# ThreadNetworkDemoApp for testing
PRODUCT_PACKAGES_DEBUG += ThreadNetworkDemoApp

Notez que vous ne devez l'ajouter qu'à la variante de débogage / eng (par exemple, PRODUCT_PACKAGES_DEBUG), car elle ne doit pas être incluse dans le build utilisateur pour les consommateurs finaux.