Cómo compilar un router de borde de Android

Ver el código fuente en Android Code Search

Si no eres un proveedor de chips Thread o de dispositivos Android, puedes dejar de leer ahora.

En este documento, se explican los pasos para compilar un nuevo dispositivo de router de borde de Thread basado en Android con el código fuente de AOSP más reciente. Si sigues este documento, aprenderás lo siguiente:

  1. la arquitectura general y el estado de la compatibilidad con Thread en Android
  2. cómo crear tu propio servicio de HAL de Thread
  3. Cómo hacer que tu dispositivo sea compatible con Google Home
  4. cómo probar tu router de borde Thread

Si necesitas asistencia, informa un problema en GitHub o abre una discusión si tienes alguna pregunta.

Descripción general

La pila de subprocesos de Android se basa en OpenThread y ot-br-posix, que Google ofrece como código abierto en GitHub. De la misma manera que OpenThread se desarrolla en un repositorio público de GitHub, la pila de Thread de Android se desarrolla en la base de código de AOSP pública. Todas las funciones y correcciones de errores se envían primero a AOSP. Esto permite que los proveedores comiencen a adoptar las versiones más recientes de Thread sin esperar a las versiones normales de Android.

Arquitectura

Toda la pila de subprocesos de Android consta de dos componentes principales: la pila de subprocesos principal en una partición del sistema genérica y el servicio de HAL de subprocesos en una partición del proveedor. Por lo general, los proveedores de dispositivos solo deben compilar el servicio de HAL.

android-thread-arch

A continuación, se muestra un breve resumen de cómo funciona la pila de subprocesos de Android: - Hay un servicio de sistema de subprocesos de Java en el servidor del sistema que administra la pila completa: proporciona la API del sistema de subprocesos, crea la interfaz de túnel thread-wpan, registra la red de subprocesos en el servicio de conectividad y, además, implementa las funciones de enrutamiento de borde y proxy de publicidad. - La pila principal de Thread / OpenThread se aloja en un proceso nativo independiente sin privilegios que se llama ot-daemon. El servicio del sistema de Java administra directamente ot-daemon a través de APIs de AIDL privadas y accede a la radio de hardware de Thread a través de la API de HAL de Thread. - Un servicio de HAL de Thread proporcionado por el proveedor DEBE implementar la API de HAL de Thread. Por lo general, funciona como un RCP y, además, implementa el protocolo spinel.

¿Dónde está el código?

Cómo configurar el entorno de desarrollo

Los proveedores de dispositivos Android que ya establecieron un entorno de desarrollo de Android para el dispositivo pueden omitir esta sección.

Si eres nuevo en el ecosistema de Android o eres un proveedor de silicio que desea hacer que tu chip Thread sea compatible con Android y brindar asistencia a los proveedores de dispositivos, sigue leyendo.

Sigue el codelab para desarrolladores de Android

Para configurar tu entorno de desarrollo de Android por primera vez, usa el siguiente codelab: https://source.android.com/docs/setup/start. Al final de este codelab, podrás compilar y ejecutar un dispositivo simulado de Cuttlefish desde el código fuente.

Compila tu servicio de HAL de Thread

Prueba Thread en Cuttlefish

Cuttlefish es el dispositivo Android virtual. Antes de comenzar a compilar tu propio servicio de HAL, es mejor probar Thread en Cuttlefish para comprender cómo funciona HAL.

Se proporciona un servicio de HAL de Thread predeterminado en Cuttlefish y se implementa con el RCP simulado, que transfiere paquetes a través de un socket UDP desde y hacia una radio Thread (802.15.4) simulada.

En la instancia de Cuttlefish, se preinstala una "ThreadNetworkDemoApp". Abre esa app para unir el dispositivo Cuttlefish a una red Thread predeterminada.

demoapp-screenshot

También se proporcionan las herramientas de línea de comandos ot-ctl y ot-cli-ftd para configurar tu red Thread para pruebas. Esas herramientas admiten todos los comandos de la CLI de OpenThread que ya conozcas.

Para buscar registros del servicio HAL de Thread de Cuttlefish, haz lo siguiente:

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

O bien, busca registros de ot-daemon con el siguiente comando:

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]

El servicio de HAL de Thread de Cuttlefish usa el servicio de HAL de Thread predeterminado más el objeto binario RCP simulado de OpenThread. Consulta la siguiente sección para ver cómo funciona.

El servicio de HAL predeterminado

Se incluye un servicio HAL predeterminado junto con la API de HAL de Thread. El servicio HAL predeterminado admite dispositivos RCP simulados y reales. Recibe una URL de dispositivo RCP opcional y, si no se proporciona la URL, se usa de forma predeterminada el dispositivo RCP simulado.

En el archivo hardware/interfaces/threadnetwork/aidl/default/threadnetwork-service.rc, haz lo siguiente:

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

Esto equivale a lo siguiente:

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

En el caso de los dispositivos RCP reales, admite interfaces SPI y UART, y puedes especificar el dispositivo con los esquemas spinel+spi://, spinel+hdlc+uart:// y spinel+socket://, respectivamente.

Comprende el APEX del proveedor

Al igual que la pila de subprocesos en el módulo de línea principal de Tethering, el servicio de HAL de subprocesos predeterminado en Cuttlefish también se empaqueta en un módulo APEX. Sin embargo, es un módulo APEX del proveedor que se instalará en /vendor/apex/ (los artefactos del módulo se descomprimirán en /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
    ],
}

Hay algunos parámetros de configuración importantes a los que deberás prestar atención o en los que deberás realizar cambios cuando compiles tu propio módulo HAL APEX:

  • file_contexts: Aquí se describen los archivos binarios o de datos que se entregan en este módulo de APEX o los archivos a los que el servicio de HAL debe acceder (por ejemplo, el dispositivo RCP). Esto te permite especificar reglas de sepolicy específicas para que tu servicio de HAL acceda al dispositivo RCP de hardware.

  • binaries: Es el archivo binario que se entrega en este módulo de APEX.

  • threadnetwork-service.rc: Indica cómo se iniciará el servicio de HAL. Debes especificar la ruta de acceso del dispositivo RCP aquí.

  • android.hardware.thread_network.prebuilt.xml: Define la función de hardware android.hardware.thread_network. Esto es necesario para que el sistema Android sepa que tu dispositivo es compatible con el hardware de Thread. De lo contrario, no se habilitará la pila de subprocesos de Android.

Crea tu servicio de HAL

Ya sea que seas desarrollador de dispositivos Android o proveedor de silicio, debes estar familiarizado con la compilación de firmware de OT RCP para tu chip Thread. En las siguientes instrucciones, se supone que el chip de hardware está conectado y validado correctamente.

La forma más sencilla de compilar tu APEX de HAL es crear un APEX nuevo con los objetos binarios y precompilados del APEX de HAL predeterminado. Por ejemplo, si tu empresa es Banana y el dispositivo RCP de tu dispositivo es /dev/ttyACM0, tu APEX de HAL de Thread se verá de la siguiente manera:

  • 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

Las rutas de acceso de los archivos en la primera columna se relacionan con /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
  }

Si suponemos que estás creando un dispositivo nuevo llamado Orange, el directorio de configuración específico del dispositivo será el siguiente:

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

Consulta la siguiente sección para ver qué reglas de sepolicy se deben agregar en el subdirectorio sepolicy/.

Reglas de Sepolicy para dispositivos RCP

De forma predeterminada, tu servicio de HAL de Thread no tiene acceso al dispositivo RCP (por ejemplo, /dev/ttyACM0). Se deben agregar reglas de política de seguridad personalizadas al directorio sepolicy/.

Crea un archivo sepolicy/threadnetwork_hal.te nuevo con el siguiente contenido:

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;

Combinación

Ahora que terminaste con casi todas las necesidades de código para agregar Thread, el último paso es agregar el APEX de HAL de Thread y las reglas de sepolicy a la imagen de tu dispositivo.

Para ello, agrega el siguiente código al Makefile de tu dispositivo (por ejemplo, device.mk):

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

Si todo funciona, ahora podrás ver el registro del servicio de HAL de Thread similar al siguiente:

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

Y el registro de ot-daemon se verá de la siguiente manera:

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

Personalización

El módulo de Thread principal (en realidad, es parte del módulo "Conexión por cable") proporciona algunas configuraciones superpuestas que los proveedores pueden especificar para personalizar el comportamiento de la pila. Consulta config_thread.xml para obtener la lista completa.

Por lo general, debes establecer config_thread_border_router_default_enabled en true para habilitar el dispositivo como router de borde de Thread y cambiar config_thread_vendor_name, config_thread_vendor_oui y config_thread_model_name a los valores de tu proveedor o producto. Esos valores se incluirán en el servicio mDNS _meshcop._udp, que siempre anuncia un router de borde de Thread.

Para agregar la superposición, debes crear un nuevo objetivo ConnectivityOverlayOrange runtime_resource_overlay para tu dispositivo Orange. Crea un directorio ConnectivityOverlay/ nuevo en device/banana/orange/rro_overlays y crea el siguiente contenido en él:

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>
  

Al igual que con el APEX de HAL, debes agregar la app superpuesta a tu archivo device.mk:

PRODUCT_PACKAGES += \
    ConnectivityOverlayOrange</code>

Si todo funciona, verás que ot-daemon registra el nombre del proveedor y del modelo al comienzo del registro:

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

Ser compatible con Google Home

Además, si deseas que el ecosistema de Google Home use tu router de borde, puedes especificar esta configuración en config_thread.xml:

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

Prueba

Ahora, tu dispositivo debería ser compatible con la especificación del router de borde de Thread 1.3 o versiones posteriores. Antes de enviarlo al programa de certificación de Thread, se deben realizar algunas pruebas de Android xTS para garantizar la compatibilidad.

  • La prueba de VTS se asegura de que el servicio de HAL de Thread funcione como se espera en tu dispositivo. Puedes ejecutar las pruebas con el comando

    atest VtsHalThreadNetworkTargetTest

  • La prueba de CTS se asegura de que las APIs de Thread funcionen según lo esperado en tu dispositivo. Puedes ejecutar las pruebas con el comando

    atest CtsThreadNetworkTestCases

  • La prueba de integración proporciona una mayor garantía de calidad sobre el funcionamiento del código principal de Thread en tu dispositivo. Puedes ejecutar las pruebas con el comando

    atest ThreadNetworkIntegrationTests

También puedes encontrar más instrucciones para ejecutar pruebas de VTS, CTS y MTS con esos conjuntos de pruebas publicados:

Prueba con la app de demostración de Thread

Al igual que con el dispositivo Cuttlefish, puedes agregar la app de demostración de Thread a tu imagen del sistema:

# ThreadNetworkDemoApp for testing
PRODUCT_PACKAGES_DEBUG += ThreadNetworkDemoApp

Ten en cuenta que solo debes agregarlo a la variante de depuración o eng (por ejemplo, PRODUCT_PACKAGES_DEBUG), ya que no se debe incluir en la compilación del usuario para los consumidores finales.