建構 Android 邊界路由器

在 Android 程式碼搜尋工具中查看來源

如果您不是 Android 裝置或 Thread 晶片供應商,可以停止閱讀。

本文件將逐步說明如何使用最新的 AOSP 原始碼,建構新的 Android 版 Thread Border Router 裝置。您可以透過本文件瞭解:

  1. Android 中執行緒支援的整體架構和狀態
  2. 如何建立自己的 Thread HAL 服務
  3. 如何讓裝置與 Google Home 相容
  4. 如何測試 Thread 邊界路由器

如需支援,請在 GitHub 中回報問題,或在有任何問題時開啟 討論

總覽

Android 執行緒堆疊是以 OpenThread 和 ot-br-posix 為基礎,這兩者皆由 Google 在 GitHub 上開放原始碼。與 OpenThread 在公開 GitHub 存放區中開發的方式相同,Android 執行緒堆疊是在公開的 AOSP 程式碼庫中開發。所有功能和錯誤修正項目都會先提交至 AOSP。這樣一來,供應商就能開始採用最新的 Thread 版本,而無須等待 Android 的定期發布。

架構

整個 Android 執行緒堆疊由兩個主要元件組成:一般系統分區中的核心執行緒堆疊,以及供應商分區中的執行緒 HAL 服務。裝置供應商通常只需要建構 HAL 服務。

android-thread-arch

以下簡要說明 Android 執行緒堆疊的運作方式:- 系統伺服器中含有 Java 執行緒系統服務,可管理整個堆疊 - 提供執行緒系統 API、建立 thread-wpan 隧道介面、將執行緒網路註冊至 Connectivity 服務,並實作邊界路由和廣告 Proxy 功能。- 核心執行緒 / OpenThread 堆疊會託管在名為 ot-daemon 的非特權獨立原生程序中。ot-daemon 會透過私人 AIDL API 由 Java 系統服務直接管理,並透過 Thread HAL API 存取 Thread 硬體無線電。- 供應商提供的 Thread HAL 服務務必實作 Thread HAL API。通常會以 RCP 的形式運作,並實作 spinel 通訊協定。

程式碼在哪裡?

設定開發環境

如果 Android 裝置供應商已為裝置建立 Android 開發環境,可以略過本節。

如果您是 Android 生態系統的新手,或是想讓 Thread 晶片與 Android 相容,並為裝置供應商提供支援的晶片供應商,請繼續閱讀。

遵循 Android 開發人員程式碼研究室

如要首次設定 Android 開發環境,請使用以下程式碼研究室:https://source.android.com/docs/setup/start。完成本程式碼研究室後,您將能夠從原始碼建構及執行模擬的 Cuttlefish 裝置。

建構 Thread HAL 服務

試用 Cuttlefish 中的 Thread

Cuttlefish 是虛擬 Android 裝置。開始建構自己的 HAL 服務前,建議您先試試 Cuttlefish 中的 Thread,瞭解 HAL 的運作方式。

Cuttlefish 提供預設的 Thread HAL 服務,並透過模擬 RCP 實作,可透過 UDP 通訊端點來回傳輸封包,並模擬 Thread (802.15.4) 無線電。

在 Cuttlefish 例項中,系統會預先安裝「ThreadNetworkDemoApp」。開啟該應用程式,將 Cuttlefish 裝置加入預設的 Thread 網路。

demoapp-screenshot

您也可以使用 ot-ctlot-cli-ftd 指令列工具設定 Thread 網路以進行測試。這些工具支援您可能已熟悉的所有 OpenThread 指令列指令。

您可以透過以下方式搜尋 Cuttlefish Thread HAL 服務的記錄:

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

或者,您也可以使用 grep 搜尋 ot-daemon 記錄檔,方法如下:

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]

Cuttlefish Thread HAL 服務會使用預設的 Thread HAL 服務,以及 OpenThread 模擬的 RCP 二進位檔,請參閱下一節瞭解其運作方式。

預設 HAL 服務

除了 Thread HAL API,系統還會提供預設 HAL 服務。預設的 HAL 服務支援模擬和實際的 RCP 裝置。它會接收選用的 RCP 裝置網址,如果未提供網址,則預設為模擬的 RCP 裝置。

在檔案 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

等同於:

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

對於實際的 RCP 裝置,它同時支援 SPI 和 UART 介面,您可以分別使用結構定義 spinel+spi://spinel+hdlc+uart://spinel+socket:// 指定裝置。

瞭解供應商 APEX

與 Tethering 主線模組中的 Thread 堆疊類似,Cuttlefish 中的預設 Thread HAL 服務也會封裝在 APEX 模組中。但這是供應商 APEX 模組,會安裝至 /vendor/apex/ (模組中的構件會解壓縮至 /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
    ],
}

建構自己的 HAL APEX 模組時,您需要留意或變更幾項重要設定:

  • file_contexts:這會說明這個 APEX 模組中提供的二進位檔 / 資料檔案,或是 HAL 服務需要存取的檔案 (例如 RCP 裝置)。這樣一來,您就能為 HAL 服務指定特定的 sepolicy 規則,以便存取硬體 RCP 裝置。

  • binaries:這個 APEX 模組中提供的二進位檔案

  • threadnetwork-service.rc:如何啟動 HAL 服務。您必須在這裡指定 RCP 裝置路徑。

  • android.hardware.thread_network.prebuilt.xml:定義 android.hardware.thread_network 硬體功能。這項資訊是 Android 系統用來判斷裝置是否支援 Thread 硬體。否則,Android 執行緒堆疊將不會啟用。

建立 HAL 服務

無論您是 Android 裝置開發人員或晶片供應商,都應熟悉為 Thread 晶片建構 OT RCP 韌體。以下操作說明假設硬體晶片已正確連接及驗證。

建構 HAL APEX 最簡單的方法,就是使用預設 HAL APEX 的二進位檔和預先建構檔建立新的 APEX。舉例來說,如果貴公司是 Banana,裝置上的 RCP 裝置是 /dev/ttyACM0,則 Thread HAL APEX 會如下所示:

  • 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

第一欄中的檔案路徑與 /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
  }

假設您正在製作名為 Orange 的新裝置,則裝置專屬設定目錄會如下所示:

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

請參閱下一節,瞭解應在 sepolicy/ 子目錄中新增哪些 sepolicy 規則。

RCP 裝置的 Sepolicy 規則

根據預設,Thread HAL 服務無法存取 RCP 裝置 (例如 /dev/ttyACM0),因此必須將自訂 sepolicy 規則新增至 sepolicy/ 目錄。

建立新的 sepolicy/threadnetwork_hal.te 檔案,並加入以下內容:

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;

整合

您現在已完成新增 Thread 所需的程式碼,最後一步是將 Thread HAL APEX 和 sepolicy 規則新增至裝置的映像檔。

方法是在裝置的 Makefile (例如 device.mk) 中新增以下程式碼:

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

如果一切正常,您現在應該會看到類似以下的 Thread HAL 服務記錄:

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

ot-daemon 記錄會如下所示:

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

自訂

執行緒主線程模組 (實際上是「Tethering」模組的一部分) 提供幾個可重疊設定,供應商可指定這些設定來自訂堆疊行為。如需完整清單,請參閱 config_thread.xml

通常,您必須將 config_thread_border_router_default_enabled 設為 true,才能讓裝置成為 Thread 邊界路由器,並將 config_thread_vendor_nameconfig_thread_vendor_ouiconfig_thread_model_name 變更為供應商或產品值。這些值會納入 _meshcop._udp mDNS 服務,該服務一律由 Thread Border Router 宣傳。

如要新增疊加層,您必須為 Orange 裝置建立新的 ConnectivityOverlayOrange runtime_resource_overlay 目標。在 device/banana/orange/rro_overlays 下建立新的 ConnectivityOverlay/ 目錄,並在其中建立以下內容:

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>
  

與 HAL APEX 類似,您需要將疊加應用程式新增至 device.mk 檔案:

PRODUCT_PACKAGES += \
    ConnectivityOverlayOrange</code>

如果一切正常運作,您會看到 ot-daemon 在記錄的開頭記錄供應商和型號名稱:

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

與 Google Home 相容

此外,如果您想讓邊界路由器供 Google Home 生態系統使用,可以在 config_thread.xml 中指定這項設定:

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

測試

你的裝置現在應可與 Thread 1.3 以上版本的邊界路由器規格相容。在將應用程式提交至 Thread 認證計畫之前,您應該執行幾項 Android xTS 測試,確保相容性。

  • VTS 測試可確保 Thread HAL 服務在裝置上正常運作。您可以使用下列指令執行測試:

    atest VtsHalThreadNetworkTargetTest

  • CTS 測試可確保 Thread API 在裝置上正常運作。您可以使用下列指令執行測試:

    atest CtsThreadNetworkTestCases

  • 整合測試可進一步保證 Thread 主線程程式碼在裝置上的運作方式。您可以使用下列指令執行測試:

    atest ThreadNetworkIntegrationTests

您也可以參閱更多操作說明,瞭解如何使用這些已發布的測試套件執行 VTS/CTS/MTS 測試:

使用 Thread 示範應用程式進行測試

與 Cuttlefish 裝置類似,您可以將 Thread 示範應用程式新增至系統映像檔:

# ThreadNetworkDemoApp for testing
PRODUCT_PACKAGES_DEBUG += ThreadNetworkDemoApp

請注意,您應該只將此值新增至偵錯 / eng 變數 (例如 PRODUCT_PACKAGES_DEBUG),因為這不應納入供最終消費者使用的使用者版本。