Android-Border-Router erstellen

Quellcode in der Android Code Search ansehen

Wenn Sie kein Android-Gerät oder kein Thread-Chipanbieter sind, können Sie jetzt aufhören zu lesen.

In diesem Dokument erfahren Sie, wie Sie ein neues Android-basiertes Thread-Border-Router-Gerät mit dem neuesten AOSP-Quellcode erstellen. In diesem Dokument erfahren Sie Folgendes:

  1. die allgemeine Architektur und den Status der Thread-Unterstützung in Android
  2. how to create your own Thread HAL service
  3. Informationen dazu, wie Sie Ihr Gerät mit Google Home kompatibel machen
  4. So testen Sie Ihren Thread-Border-Router

Wenn Sie Support benötigen, erstellen Sie einen Problemfall in GitHub oder eröffnen Sie eine Diskussion, wenn Sie Fragen haben.

Übersicht

Der Android-Thread-Stack basiert auf OpenThread und ot-br-posix, die von Google auf GitHub als Open Source veröffentlicht wurden. Genauso wie OpenThread in einem öffentlichen GitHub-Repository entwickelt wird, wird der Android-Thread-Stack in der öffentlichen AOSP-Codebasis entwickelt. Alle Funktionen und Fehlerkorrekturen werden zuerst in AOSP eingereicht. So können Anbieter die neuesten Thread-Versionen einführen, ohne auf regelmäßige Android-Releases warten zu müssen.

Architektur

Der gesamte Android-Thread-Stack besteht aus zwei Hauptkomponenten: dem Kern-Thread-Stack in einer generischen Systempartition und dem Thread HAL-Dienst in einer Anbieterpartition. Geräteanbieter müssen in der Regel nur den HAL-Dienst erstellen.

android-thread-arch

Hier eine kurze Zusammenfassung der Funktionsweise des Android-Thread-Stacks: – Es gibt einen Java-Thread-Systemdienst auf dem Systemserver, der den gesamten Stack verwaltet. Er stellt die Thread-System-API bereit, erstellt die thread-wpan-Tunnelschnittstelle, registriert das Thread-Netzwerk beim Konnektivitätsdienst und implementiert die Border-Routing- und Advertising-Proxy-Funktionen. – Der Thread-/OpenThread-Stack wird in einem nicht privilegierten eigenständigen nativen Prozess namens ot-daemon gehostet. ot-daemon wird direkt vom Java-Systemdienst über private AIDL APIs verwaltet und greift über die Thread HAL API auf die Thread-Hardwareschnittstelle zu. – Ein vom Anbieter bereitgestellter Thread HAL-Dienst MUSS die Thread HAL API implementieren. Sie arbeitet in der Regel als RCP und implementiert das spinel-Protokoll.

Wo finde ich den Code?

Entwicklungsumgebung einrichten

Android-Gerätehersteller, die bereits eine Android-Entwicklungsumgebung für das Gerät eingerichtet haben, können diesen Abschnitt überspringen.

Wenn Sie neu im Android-System sind oder ein Chipanbieter sind, der seinen Thread-Chip mit Android kompatibel machen und Support für Geräteanbieter bereitstellen möchte, lesen Sie weiter.

Codelab für Android-Entwickler

Wenn Sie Ihre Android-Entwicklungsumgebung zum ersten Mal einrichten möchten, verwenden Sie das folgende Codelab: https://source.android.com/docs/setup/start. Am Ende dieses Codelabs können Sie ein simuliertes Cuttlefish-Gerät aus dem Quellcode erstellen und ausführen.

Thread HAL-Dienst erstellen

Thread in Cuttlefish ausprobieren

Cuttlefish ist das virtuelle Android-Gerät. Bevor Sie mit dem Erstellen Ihres eigenen HAL-Dienstes beginnen, sollten Sie Thread in Cuttlefish ausprobieren, um zu verstehen, wie HAL funktioniert.

In Cuttlefish wird ein standardmäßiger Thread HAL-Dienst bereitgestellt, der mit dem simulierten RCP implementiert ist, der Pakete über einen UDP-Socket von und zu einem simulierten Thread-Funkschnittstellen (802.15.4) sendet und empfängt.

In der Cuttlefish-Instanz ist die „ThreadNetworkDemoApp“ vorinstalliert. Öffnen Sie diese App, um das Cuttlefish-Gerät mit einem Standard-Thread-Netzwerk zu verbinden.

demoapp-screenshot

Außerdem gibt es die Befehlszeilentools ot-ctl und ot-cli-ftd, mit denen Sie Ihr Thread-Netzwerk für den Test konfigurieren können. Diese Tools unterstützen alle OpenThread-Befehle für die Befehlszeile, die Sie möglicherweise bereits kennen.

So können Sie mit Grep nach Logs des Cuttlefish Thread HAL-Dienstes suchen:

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

Oder Sie können mit grep nach ot-Daemon-Logs suchen:

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]

Der Cuttlefish-Thread-HAL-Dienst verwendet den standardmäßigen Thread-HAL-Dienst sowie das simulierte RCP-Binärprogramm von OpenThread. Im nächsten Abschnitt wird die Funktionsweise erläutert.

Der Standard-HAL-Dienst

Neben der Thread HAL API ist auch ein Standard-HAL-Dienst enthalten. Der Standard-HAL-Dienst unterstützt sowohl simulierte als auch echte RCP-Geräte. Es erhält eine optionale RCP-Geräte-URL. Wenn die URL nicht angegeben ist, wird standardmäßig das simulierte RCP-Gerät verwendet.

In der Datei 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

Dies entspricht:

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

Für echte RCP-Geräte werden sowohl SPI- als auch UART-Schnittstellen unterstützt. Sie können das Gerät mit den Schemas spinel+spi://, spinel+hdlc+uart:// und spinel+socket:// angeben.

APEX des Anbieters

Ähnlich wie der Thread-Stack im Tethering-Mainline-Modul ist auch der standardmäßige Thread-HAL-Dienst in Cuttlefish in einem APEX-Modul verpackt. Es ist jedoch ein APEX-Modul eines Anbieters, das unter /vendor/apex/ installiert wird. Die Artefakte im Modul werden in /apex/com.android.hardware.threadnetwork/ entpackt.

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
    ],
}

Es gibt einige wichtige Konfigurationen, die Sie beim Erstellen Ihres eigenen HAL-APEX-Moduls beachten oder ändern müssen:

  • file_contexts: Hier werden die Binär-/Dateien beschrieben, die in diesem APEX-Modul bereitgestellt werden, oder Dateien, auf die der HAL-Dienst zugreifen muss (z. B. das RCP-Gerät). So können Sie für Ihren HAL-Dienst bestimmte SEPolicy-Regeln für den Zugriff auf das Hardware-RCP-Gerät angeben.

  • binaries: Die Binärdatei, die in diesem APEX-Modul bereitgestellt wird

  • threadnetwork-service.rc: Wie der HAL-Dienst gestartet wird. Hier müssen Sie den RCP-Gerätepfad angeben.

  • android.hardware.thread_network.prebuilt.xml: Definiert die android.hardware.thread_network-Hardwarefunktion. Das ist erforderlich, damit das Android-System weiß, dass Ihr Gerät Thread-Hardware unterstützt. Andernfalls wird der Android-Thread-Stack nicht aktiviert.

HAL-Dienst erstellen

Unabhängig davon, ob Sie ein Android-Geräteentwickler oder ein Chipanbieter sind, sollten Sie mit der Erstellung von OT RCP-Firmware für Ihren Thread-Chip vertraut sein. In der folgenden Anleitung wird davon ausgegangen, dass der Hardwarechip richtig verkabelt und validiert ist.

Am einfachsten erstellen Sie Ihre HAL-APEX, indem Sie eine neue APEX mit den Binärdateien und Prebuilts der Standard-HAL-APEX erstellen. Wenn Ihr Unternehmen beispielsweise „Apfel“ heißt und das RCP-Gerät auf Ihrem Gerät /dev/ttyACM0 ist, sieht Ihre Thread HAL APEX so aus:

  • 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

Die Dateipfade in der ersten Spalte beziehen sich auf /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
  }

Angenommen, Sie erstellen ein neues Gerät mit dem Namen „Orange“, sieht das entsprechende Konfigurationsverzeichnis so aus:

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

Im nächsten Abschnitt erfahren Sie, welche Sepolicy-Regeln im Unterverzeichnis sepolicy/ hinzugefügt werden sollten.

Sepolicy-Regeln für RCP-Gerät

Standardmäßig hat Ihr Thread HAL-Dienst keinen Zugriff auf das RCP-Gerät (z. B. /dev/ttyACM0). Dem Verzeichnis sepolicy/ müssen benutzerdefinierte SEPolicy-Regeln hinzugefügt werden.

Erstellen Sie eine neue sepolicy/threadnetwork_hal.te-Datei mit folgendem Inhalt:

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;

Zusammenstellen

Sie haben jetzt fast den gesamten Code für das Hinzufügen von Thread fertiggestellt. Als letzten Schritt müssen Sie dem Image Ihres Geräts die Thread HAL APEX- und sepolicy-Regeln hinzufügen.

Fügen Sie dazu den folgenden Code in die Makefile Ihres Geräts ein (z. B. device.mk):

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

Wenn alles funktioniert, sehen Sie jetzt das Thread HAL-Dienstprotokoll, das in etwa so aussieht:

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

Das ot-daemon-Protokoll sieht dann so aus:

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

Anpassung

Das Thread-Mainline-Modul (tatsächlich Teil des „Tethering“-Moduls) bietet einige Overlay-Konfigurationen, die von Anbietern angegeben werden können, um das Stack-Verhalten anzupassen. Eine vollständige Liste findest du unter config_thread.xml.

Normalerweise müssen Sie config_thread_border_router_default_enabled auf true setzen, um Ihr Gerät als Thread-Border-Router zu aktivieren, und config_thread_vendor_name, config_thread_vendor_oui und config_thread_model_name auf die Werte Ihres Anbieters oder Produkts ändern. Diese Werte werden in den _meshcop._udp-mDNS-Dienst aufgenommen, der immer von einem Thread-Border-Router beworben wird.

Wenn Sie das Overlay hinzufügen möchten, müssen Sie ein neues Ziel vom Typ ConnectivityOverlayOrange runtime_resource_overlay für Ihr Orange-Gerät erstellen. Erstellen Sie unter device/banana/orange/rro_overlays ein neues Verzeichnis ConnectivityOverlay/ und fügen Sie ihm die folgenden Inhalte hinzu:

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>
  

Ähnlich wie bei HAL APEX müssen Sie die Overlay-App der device.mk-Datei hinzufügen:

PRODUCT_PACKAGES += \
    ConnectivityOverlayOrange</code>

Wenn alles funktioniert, sehen Sie, dass ot-daemon den Namen des Anbieters und des Modells ganz am Anfang des Logs protokolliert:

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

mit Google Home kompatibel sein

Wenn Sie möchten, dass Ihr Border-Router vom Google Home-System verwendet wird, können Sie diese Konfiguration in config_thread.xml angeben:

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

Test

Ihr Gerät sollte jetzt mit der Border-Router-Spezifikation von Thread 1.3 und höher kompatibel sein. Bevor Sie das Gerät an das Thread-Zertifizierungsprogramm senden, sollten Sie einige Android-xTS-Tests durchführen, um die Kompatibilität zu prüfen.

  • Der VTS-Test stellt sicher, dass der Thread HAL-Dienst auf Ihrem Gerät wie erwartet funktioniert. Sie können die Tests mit dem Befehl

    atest VtsHalThreadNetworkTargetTest

  • Der CTS-Test stellt sicher, dass Thread-APIs auf Ihrem Gerät wie erwartet funktionieren. Sie können die Tests mit dem Befehl

    atest CtsThreadNetworkTestCases

  • Der Integrationstest bietet eine bessere Qualitätsgarantie dafür, wie der Thread-Mainline-Code auf Ihrem Gerät funktioniert. Sie können die Tests mit dem Befehl

    atest ThreadNetworkIntegrationTests

Weitere Anleitungen zum Ausführen von VTS-/CTS-/MTS-Tests mit diesen veröffentlichten Test-Suites:

Mit der Thread-Demo-App testen

Ähnlich wie beim Cuttlefish-Gerät können Sie Ihrem System-Image die Thread-Demo-App hinzufügen:

# ThreadNetworkDemoApp for testing
PRODUCT_PACKAGES_DEBUG += ThreadNetworkDemoApp

Hinweis: Sie sollten sie nur der Debug-/Eng-Variante hinzufügen (z. B. PRODUCT_PACKAGES_DEBUG), da sie nicht in den Nutzerbuild für Endverbraucher aufgenommen werden soll.