Tworzenie routera granicznego na Androida

Wyświetlanie kodu źródłowego w Android Code Search

Jeśli nie jesteś producentem urządzenia z Androidem ani producentem układu Thread, możesz już przestać czytać.

Ten dokument zawiera instrukcje tworzenia nowego urządzenia z Androidem Thread Border Router z najnowszym kodem źródłowym AOSP. Z tego dokumentu dowiesz się:

  1. ogólna architektura i stan obsługi Thread na Androidzie
  2. jak utworzyć własną usługę Thread HAL
  3. jak sprawić, aby urządzenie było zgodne z Google Home
  4. jak przetestować router graniczny Thread

Jeśli potrzebujesz pomocy, zgłoś problem na GitHub lub otwórz dyskusję, jeśli masz pytania.

Omówienie

Grupa wątków Androida opiera się na OpenThread i ot-br-posix, które są dostępne w formie open source na GitHubie. Podobnie jak OpenThread, jest on rozwijany w publicznym repozytorium GitHub, więc stos wątków Androida jest rozwijany w publicznej bazie kodu AOSP. Wszystkie funkcje i poprawki błędów są najpierw przesyłane do AOSP. Dzięki temu dostawcy mogą zacząć stosować najnowsze wersje Thread bez oczekiwania na regularne wersje Androida.

Architektura

Cały stos wątków Androida składa się z 2 głównych komponentów: podstawowego stosu wątków na ogólnym partycji systemu i usługi Thread HAL na partycji dostawcy. Producenci urządzeń zwykle muszą tylko zająć się stworzeniem usługi HAL.

android-thread-arch

Oto krótkie podsumowanie działania wątku w Androidzie: – Na serwerze systemowym jest usługa systemu wątku w języku Java, która zarządza całym stosem – udostępnia interfejs API systemu wątku, tworzy interfejs tunelu thread-wpan, rejestruje sieć wątku w usłudze Connectivity i wdraża funkcje Border Routing i Advertising Proxy. – Główny pakiet Thread lub OpenThread jest hostowany w nieuprzywilejowanym procesie natywnym o nazwie ot-daemon. ot-daemon jest zarządzany bezpośrednio przez usługę systemową Java za pomocą prywatnych interfejsów AIDL API i uzyskuje dostęp do radia Thread za pomocą interfejsu Thread HAL API. – Usługa Thread HAL dostarczona przez dostawcę MUSI implementować interfejs Thread HAL API. Zazwyczaj działa jako RCP i implementuje protokół spinel.

Gdzie jest kod?

Konfigurowanie środowiska programistycznego

Dostawcy urządzeń z Androidem, którzy już utworzyli środowisko programistyczne Androida na urządzeniu, mogą pominąć tę sekcję.

Jeśli dopiero zaczynasz korzystać z ekosystemu Androida lub jesteś dostawcą układów scalonych, który chce zapewnić zgodność swojego modułu Thread z Androidem i obsługę dla dostawców urządzeń, czytaj dalej.

Wykonaj ćwiczenia z programowania dla deweloperów aplikacji na Androida

Aby po raz pierwszy skonfigurować środowisko programistyczne Androida, skorzystaj z tego ćwiczenia z programowania: https://source.android.com/docs/setup/start. Na koniec tego ćwiczenia będziesz mieć możliwość utworzenia i uruchomienia symulowanego urządzenia Cuttlefish na podstawie kodu źródłowego.

Tworzenie usługi Thread HAL

Wypróbuj wątek w Cuttlefish

Cuttlefish to wirtualne urządzenie z Androidem. Zanim zaczniesz tworzyć własną usługę HAL, lepiej najpierw zapoznaj się z działaniem wątków w Cuttlefish.

W Cuttlefish jest dostępna domyślna usługa Thread HAL, która jest implementowana za pomocą symulowanego RCP, który przesyła i odbiera pakiety za pomocą gniazda UDP do i z symulowanego radia Thread (802.15.4).

W przypadku instancji Cuttlefish aplikacja „ThreadNetworkDemoApp” jest wstępnie zainstalowana. Otwórz tę aplikację, aby połączyć urządzenie Cuttlefish z domyślną siecią Thread.

demoapp-screenshot

Dostępne są też narzędzia wiersza poleceń ot-ctlot-cli-ftd, które umożliwiają skonfigurowanie sieci Thread na potrzeby testów. Te narzędzia obsługują wszystkie polecenia OpenThread CLI, które mogą być Ci już znane.

Aby wyszukać dzienniki usługi Cuttlefish Thread HAL, wykonaj te czynności:

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

Możesz też wyszukać dzienniki demona ot, wykonując jedną z tych czynności:

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]
.

Usługa Cuttlefish Thread HAL korzysta z domyślnej usługi Thread HAL oraz z binarnego pliku RCP symulowanego przez OpenThread. Więcej informacji znajdziesz w następnej sekcji.

Domyślna usługa HAL

Wraz z interfejsem Thread HAL API jest dołączona domyślna usługa HAL. Domyślna usługa HAL obsługuje zarówno symulowane, jak i rzeczywiste urządzenia RCP. Otrzymuje opcjonalny URL urządzenia RCP. Jeśli nie zostanie podany, domyślnie zostanie użyte symulowane urządzenie RCP.

W pliku 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

Jest to równoważne:

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

W przypadku prawdziwych urządzeń RCP obsługuje interfejsy SPI i UART. Urządzenie można określić za pomocą schematu spinel+spi://, spinel+hdlc+uart:// lub spinel+socket://.

Omówienie dostawcy APEX

Podobnie jak w przypadku pakietu Thread w module głównego wiązania, domyślna usługa Thread HAL w Cuttlefish jest również spakowana w moduł APEX. Jest to jednak moduł APEX dostawcy, który zostanie zainstalowany w folderze /vendor/apex/ (elementy w module zostaną rozpakowane do folderu /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
    ],
}

Podczas tworzenia własnego modułu HAL APEX musisz zwrócić uwagę na kilka ważnych konfiguracji lub wprowadzić w nich zmiany:

  • file_contexts: zawiera opis plików binarnych lub danych dostarczanych w tym module APEX lub plików, do których usługa HAL musi mieć dostęp (np. urządzenia RCP). Umożliwia to określenie konkretnych reguł sepolicy dla usługi HAL, aby uzyskać dostęp do sprzętowego urządzenia RCP.

  • binaries: plik binarny dostarczony w tym module APEX

  • threadnetwork-service.rc: sposób uruchamiania usługi HAL. Musisz tutaj określić ścieżkę urządzenia RCP.

  • android.hardware.thread_network.prebuilt.xml: definiuje funkcję sprzętową android.hardware.thread_network. Jest to konieczne, aby system Android wiedział, że Twoje urządzenie ma sprzętową obsługę Thread. W przeciwnym razie stos wątków Androida nie zostanie włączony.

Tworzenie usługi HAL

Niezależnie od tego, czy jesteś deweloperem urządzeń z Androidem, czy dostawcą układów scalonych, powinieneś znać proces tworzenia oprogramowania układowego OT RCP dla chipa Thread. W tych instrukcjach przyjęto, że układ scalony jest prawidłowo podłączony i zweryfikowany.

Najprostszym sposobem skompilowania HAL APEX jest utworzenie nowego APEX z binarnymi plikami i kompletnymi pakietami domyślnego HAL APEX. Jeśli na przykład Twoja firma to Banana, a urządzenie RCP na Twoim urządzeniu to /dev/ttyACM0, Twoja usługa HAL APEX dla Thread będzie wyglądać tak:

  • 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

Ścieżki do plików w pierwszej kolumnie są powiązane z /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
  }

Zakładając, że tworzysz nowe urządzenie o nazwie Orange, katalog konfiguracji będzie wyglądał tak:

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

W następnej sekcji znajdziesz informacje o tym, jakie reguły sepolicy należy dodać w podkatalogu sepolicy/.

Reguły Sepolicy dla urządzenia RCP

Domyślnie usługa HAL Thread nie ma dostępu do urządzenia RCP (na przykład /dev/ttyACM0). Aby to umożliwić, do katalogu sepolicy/ należy dodać niestandardowe reguły sepolicy.

Utwórz nowy plik sepolicy/threadnetwork_hal.te z podanymi niżej danymi:

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;

Złożenie

Teraz masz już prawie cały kod potrzebny do dodania Thread. Ostatnim krokiem jest dodanie do obrazu urządzenia pliku APEX Thread HAL i reguł sepolicy.

Aby to zrobić, dodaj podany niżej kod do Makefile na urządzeniu (na przykład device.mk):

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

Jeśli wszystko działa prawidłowo, zobaczysz dziennik usługi Thread HAL podobny do tego:

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

A log ot-daemon będzie wyglądał tak:

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

Dostosowywanie

Główny moduł Thread (jest on częścią modułu „Tethering”) udostępnia kilka konfiguracji nakładających się na siebie, które mogą być określane przez dostawców w celu dostosowania zachowania modułu. Pełną listę znajdziesz w pliku config_thread.xml.

Aby umożliwić korzystanie z urządzenia jako routera granicznego Thread, musisz zazwyczaj ustawić wartość parametru config_thread_border_router_default_enabled na true, a także zmienić wartości parametrów config_thread_vendor_name, config_thread_vendor_ouiconfig_thread_model_name na wartości od dostawcy lub producenta. Te wartości będą uwzględniane w usłudze _meshcop._udp mDNS, która jest zawsze reklamowana przez router graniczny Thread.

Aby dodać nakładkę, musisz utworzyć nowy cel ConnectivityOverlayOrange runtime_resource_overlay dla urządzenia Orange. Utwórz nowy katalog ConnectivityOverlay/ w katalogu device/banana/orange/rro_overlays i dodaj w nim te elementy:

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>
  

Podobnie jak w przypadku HAL APEX, musisz dodać aplikację nakładki do pliku device.mk:

PRODUCT_PACKAGES += \
    ConnectivityOverlayOrange</code>

Jeśli wszystko działa prawidłowo, zobaczysz, że ot-daemon rejestruje nazwę dostawcy i modela na samym początku dziennika:

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

być zgodne z Google Home.

Jeśli chcesz, aby router graniczny był używany przez ekosystem Google Home, możesz określić tę konfigurację w config_thread.xml:

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

Testowanie

Urządzenie powinno być teraz zgodne ze specyfikacją routera granicznego Thread w wersji 1.3 lub nowszej. Zanim prześlesz urządzenie do programu certyfikacji Thread, musisz przeprowadzić kilka testów Androida xTS, aby sprawdzić jego zgodność.

  • Test VTS sprawdza, czy usługa HAL Thread działa zgodnie z oczekiwaniami na urządzeniu. Testy możesz uruchomić za pomocą polecenia

    atest VtsHalThreadNetworkTargetTest

  • Test CTS sprawdza, czy interfejsy API Thread działają zgodnie z oczekiwaniami na urządzeniu. Możesz uruchamiać testy za pomocą polecenia

    atest CtsThreadNetworkTestCases

  • Test integracji zapewnia większą gwarancję jakości kodu głównego Thread na Twoim urządzeniu. Testy możesz uruchomić za pomocą polecenia

    atest ThreadNetworkIntegrationTests

Więcej instrukcji dotyczących uruchamiania testów VTS, CTS i MTS znajdziesz w tych opublikowanych zestawach testów:

Testowanie za pomocą aplikacji demonstracyjnej Thread

Podobnie jak w przypadku urządzenia Cuttlefish, możesz dodać aplikację demonstracyjną Thread do obrazu systemu:

# ThreadNetworkDemoApp for testing
PRODUCT_PACKAGES_DEBUG += ThreadNetworkDemoApp

Pamiętaj, że należy dodać go tylko do wersji debugowania lub wersji inżynierskiej (np. PRODUCT_PACKAGES_DEBUG), ponieważ nie powinien on być uwzględniany w wersji użytkownika przeznaczonej dla użytkowników końcowych.