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:
- la arquitectura general y el estado de la compatibilidad con Thread en Android
- cómo crear tu propio servicio de HAL de Thread
- Cómo hacer que tu dispositivo sea compatible con Google Home
- 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.
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?
- El framework, la API y el servicio de Thread de Android: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Connectivity/thread/
- La API de HAL de Thread y la implementación del servicio predeterminado: https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/threadnetwork/
- Repositorio de OpenThread importado: https://cs.android.com/android/platform/superproject/main/+/main:external/openthread/
- Repositorio importado de ot-br-posix: https://cs.android.com/android/platform/superproject/main/+/main:external/ot-br-posix/
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.
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 hardwareandroid.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:
- https://source.android.com/docs/core/tests/vts
- https://source.android.com/docs/compatibility/cts/run
- https://docs.partner.android.com/mainline/test/mts (debes ser socio para acceder a este vínculo)
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.