Criar um roteador de borda do Android

Ver a origem na Pesquisa de código do Android

Se você não é um fornecedor de dispositivos Android ou de chips Thread, pode parar de ler agora.

Este documento orienta você nas etapas para criar um novo dispositivo de roteador de borda Thread baseado no Android com o código-fonte AOSP mais recente. Ao seguir este documento, você vai aprender:

  1. a arquitetura geral e o status do suporte a Thread no Android
  2. como criar seu próprio serviço HAL do Thread
  3. como tornar seu dispositivo compatível com o Google Home
  4. como testar o roteador de borda do Thread

Se precisar de suporte, registre um problema no GitHub ou abra uma discussão se tiver dúvidas.

Visão geral

A pilha de linhas de execução do Android é baseada no OpenThread e no ot-br-posix, que são de código aberto do Google no GitHub. Da mesma forma que o OpenThread é desenvolvido em um repositório público do GitHub, a pilha do Android Thread é desenvolvida na base de código pública do AOSP. Todos os recursos e correções de bugs são enviados primeiro no AOSP. Isso permite que os fornecedores comecem a adotar as versões mais recentes do Thread sem esperar pelas versões regulares do Android.

Arquitetura

A pilha de linhas de execução do Android consiste em dois componentes principais: a pilha de linhas de execução principal em uma partição de sistema genérica e o serviço HAL de linha de execução em uma partição do fornecedor. Os fornecedores de dispositivos geralmente precisam apenas cuidar e criar o serviço HAL.

android-thread-arch

Confira um breve resumo de como a pilha de linhas de execução do Android funciona: - Há um serviço de sistema de linha de execução Java no servidor do sistema que gerencia toda a pilha, fornece a API do sistema de linha de execução, cria a interface de túnel thread-wpan, registra a rede Thread no serviço de conectividade e implementa as funcionalidades de roteamento de borda e proxy de publicidade. - A pilha Thread / OpenThread principal é hospedada em um processo nativo autônomo sem privilégios chamado ot-daemon. O ot-daemon é gerenciado diretamente pelo serviço de sistema Java usando APIs AIDL privadas e acessa o rádio de hardware do Thread pela API HAL do Thread. - Um serviço HAL do Thread fornecido pelo fornecedor PRECISA implementar a API HAL do Thread. Ele geralmente funciona como um RCP e implementa o protocolo spinel.

Onde está o código?

Configurar o ambiente de desenvolvimento

Os fornecedores de dispositivos Android que já estabeleceram um ambiente de desenvolvimento Android para o dispositivo podem pular esta seção.

Se você é novo no ecossistema do Android ou é um fornecedor de silício que quer tornar seu chip Thread compatível com o Android e oferecer suporte a fornecedores de dispositivos, continue lendo.

Siga o codelab para desenvolvedores Android

Para configurar o ambiente de desenvolvimento Android pela primeira vez, use o codelab a seguir: https://source.android.com/docs/setup/start. Ao final deste codelab, você vai conseguir criar e executar um dispositivo Cuttlefish simulado a partir do código-fonte.

Criar o serviço HAL do Thread

Testar a conversa no Cuttlefish

O Cuttlefish é o dispositivo Android virtual. Antes de começar a criar seu próprio serviço HAL, é melhor testar a linha de execução no Cuttlefish para entender como o HAL funciona.

Um serviço HAL de padrão de Thread é fornecido no Cuttlefish e implementado com o RCP simulado, que recebe e envia pacotes via soquete UDP para e de um rádio Thread (802.15.4) simulado.

Na instância do Cuttlefish, um "ThreadNetworkDemoApp" é pré-instalado. Abra esse app para conectar o dispositivo Cuttlefish a uma rede Thread padrão.

demoapp-screenshot

Também há as ferramentas de linha de comando ot-ctl e ot-cli-ftd para configurar a rede Thread para testes. Essas ferramentas oferecem suporte a todos os comandos da CLI do OpenThread que você já conhece.

É possível pesquisar registros do serviço HAL da linha Cuttlefish da seguinte maneira:

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

Ou use o grep para encontrar registros do daemon ot:

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]

O serviço HAL de linhagem Cuttlefish usa o serviço HAL de linhagem padrão e o binário RCP simulado do OpenThread. Consulte a próxima seção para saber como ele funciona.

O serviço HAL padrão

Um serviço HAL padrão é incluído com a API HAL do Thread. O serviço HAL padrão oferece suporte a dispositivos RCP simulados e reais. Ele recebe um URL de dispositivo RCP opcional e, se o URL não for fornecido, ele será definido como o dispositivo RCP simulado.

No arquivo 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

Isso é equivalente a:

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

Para dispositivos RCP reais, ele oferece suporte às interfaces SPI e UART, e você pode especificar o dispositivo com o esquema spinel+spi://, spinel+hdlc+uart:// e spinel+socket://, respectivamente.

Entender o APEX do fornecedor

Semelhante à pilha de linhas de execução no módulo principal de tethering, o serviço HAL de linha de execução padrão no Cuttlefish também é empacotado em um módulo APEX. No entanto, é um módulo APEX do fornecedor que será instalado em /vendor/apex/. Os artefatos no módulo serão descompactados em /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
    ],
}

Há algumas configurações importantes que você precisa prestar atenção ou fazer alterações ao criar seu próprio módulo HAL APEX:

  • file_contexts: descreve os arquivos binários / de dados enviados neste módulo APEX ou os arquivos que o serviço HAL precisa acessar (por exemplo, o dispositivo RCP). Isso permite especificar regras de política de segurança específicas para que seu serviço HAL acesse o dispositivo RCP de hardware.

  • binaries: o arquivo binário entregue neste módulo do APEX

  • threadnetwork-service.rc: como o serviço HAL será iniciado. É necessário especificar o caminho do dispositivo RCP aqui.

  • android.hardware.thread_network.prebuilt.xml: define o recurso de hardware android.hardware.thread_network. Isso é necessário para que o sistema Android saiba que o dispositivo tem suporte a hardware Thread. Caso contrário, a pilha de linhas de execução do Android não será ativada.

Criar o serviço HAL

Seja você um desenvolvedor de dispositivos Android ou um fornecedor de silício, é importante conhecer a criação de firmware OT RCP para seu chip Thread. As instruções a seguir pressupõem que o chip de hardware está conectado e validado corretamente.

A maneira mais simples de criar seu HAL APEX é criar um novo APEX com os binários e pré-criados do HAL APEX padrão. Por exemplo, se a sua empresa for Banana e o dispositivo RCP no seu dispositivo for /dev/ttyACM0, o APEX HAL do Thread vai ficar assim:

  • 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

Os caminhos dos arquivos na primeira coluna estão relacionados a /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
  }

Supondo que você esteja criando um novo dispositivo chamado "Laranja", o diretório de configuração específico do dispositivo será:

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

Consulte a próxima seção para saber quais regras de sepolicy precisam ser adicionadas no subdiretório sepolicy/.

Regras de Sepolicy para dispositivos RCP

Por padrão, o serviço HAL do Thread não tem acesso ao dispositivo RCP (por exemplo, /dev/ttyACM0). As regras de política de segurança personalizadas precisam ser adicionadas ao diretório sepolicy/.

Crie um novo arquivo sepolicy/threadnetwork_hal.te com o conteúdo abaixo:

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;

Reunir

Agora que você terminou quase todas as necessidades de código para adicionar a Thread, a última etapa é adicionar o APEX do HAL da Thread e as regras de sepolicy à imagem do dispositivo.

Para fazer isso, adicione o código abaixo à Makefile do dispositivo (por exemplo, device.mk):

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

Se tudo funcionar, você vai poder conferir o registro de serviço HAL do Thread semelhante a este:

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

E o registro ot-daemon será assim:

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

Personalização

O módulo principal da Thread (que faz parte do módulo "Tethering") oferece algumas configurações sobrepostas que podem ser especificadas pelos fornecedores para personalizar o comportamento da pilha. Consulte config_thread.xml para conferir a lista completa.

Normalmente, é necessário definir config_thread_border_router_default_enabled como true para ativar o dispositivo como um roteador de borda do Thread e mudar o config_thread_vendor_name, config_thread_vendor_oui e config_thread_model_name para os valores do fornecedor ou do produto. Esses valores serão incluídos no serviço mDNS _meshcop._udp, que é sempre anunciado por um roteador de borda Thread.

Para adicionar a sobreposição, crie uma nova meta ConnectivityOverlayOrange runtime_resource_overlay para o dispositivo Orange. Crie um novo diretório ConnectivityOverlay/ em device/banana/orange/rro_overlays e crie o conteúdo abaixo nele:

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>
  

Assim como no HAL APEX, é necessário adicionar o app de sobreposição ao arquivo device.mk:

PRODUCT_PACKAGES += \
    ConnectivityOverlayOrange</code>

Se tudo funcionar, você vai notar que ot-daemon registra o nome do fornecedor e do modelo no início do 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 compatível com o Google Home

Além disso, se você quiser que o roteador de borda seja usado pelo ecossistema do Google Home, especifique essa configuração em config_thread.xml:

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

Teste

Seu dispositivo precisa ser compatível com a especificação do roteador de borda do Thread 1.3 ou mais recente. Antes de enviar ao programa de certificação do Thread, é necessário realizar alguns testes do xTS do Android para garantir a compatibilidade.

  • O teste VTS garante que o serviço HAL do Thread funcione conforme o esperado no dispositivo. É possível executar os testes com o comando

    atest VtsHalThreadNetworkTargetTest

  • O teste do CTS garante que as APIs do Thread funcionem conforme o esperado no seu dispositivo. É possível executar os testes com o comando

    atest CtsThreadNetworkTestCases

  • O teste de integração oferece mais garantia de qualidade sobre como o código principal da linha de execução da Thread funciona no seu dispositivo. É possível executar os testes com o comando

    atest ThreadNetworkIntegrationTests

Você também pode encontrar mais instruções sobre como executar testes VTS/CTS/MTS com esses conjuntos de testes lançados:

Testar com o app de demonstração da linha de execução

Assim como no dispositivo Cuttlefish, é possível adicionar o app de demonstração de linha de execução à imagem do sistema:

# ThreadNetworkDemoApp for testing
PRODUCT_PACKAGES_DEBUG += ThreadNetworkDemoApp

Adicione-o apenas à variante de depuração / eng (por exemplo, PRODUCT_PACKAGES_DEBUG), já que ele não pode ser incluído no build do usuário para consumidores finais.