构建 Android 边界路由器

在 Android 代码搜索中查看源代码

如果您不是 Android 设备或 Thread 芯片供应商,则不必继续阅读了。

本文档将引导您使用最新的 AOSP 源代码,逐步构建新的基于 Android 的 Thread 边界路由器设备。通过本文档,您将了解:

  1. Android 中线程支持的整体架构和状态
  2. 如何创建自己的线程 HAL 服务
  3. 如何让您的设备与 Google Home 兼容
  4. 如何测试 Thread 边界路由器

如果您需要支持,请在 GitHub 中提交问题;如果您有任何疑问,请发起讨论

概览

Android 线程堆栈基于 Google 在 GitHub 上开源的 OpenThread 和 ot-br-posix。与 OpenThread 在公开 GitHub 代码库中开发的方式一样,Android 线程堆栈也是在公开的 AOSP 代码库中开发的。所有功能和 bug 修复程序都先提交到 AOSP。这样,供应商无需等待常规 Android 版本,即可开始采用最新的 Thread 版本。

架构

整个 Android 线程堆栈由两个主要组件组成:通用系统分区中的核心线程堆栈,以及供应商分区中的线程 HAL 服务。设备供应商通常只需照顾和构建 HAL 服务。

android-thread-arch

下面简要介绍了 Android 线程堆栈的运作方式: - 系统服务器中有一个 Java 线程系统服务,用于管理整个堆栈 - 提供线程系统 API、创建 thread-wpan 隧道接口、将线程网络注册到连接服务,并实现边界路由和广告代理功能。 - 核心 Thread / OpenThread 堆栈托管在名为 ot-daemon 的非特权独立原生进程中。ot-daemon 由 Java 系统服务通过专用 AIDL API 直接管理,并通过 Thread HAL API 访问 Thread 硬件无线电。- 供应商提供的 Thread HAL 服务必须实现 Thread HAL API。它通常用作 RCP 并实现 spinel 协议。

代码在哪里?

设置开发环境

如果 Android 设备供应商已为设备建立 Android 开发环境,则可以跳过本部分。

如果您刚刚接触 Android 生态系统,或者您是希望让自己的 Thread 芯片与 Android 兼容并为设备供应商提供支持的芯片供应商,请继续阅读。

按照 Android 开发者 Codelab 中的说明操作

如需首次设置 Android 开发环境,请使用以下 Codelab:https://source.android.com/docs/setup/start。在此 Codelab 结束时,您将能够从源代码构建和运行模拟的 Cuttlefish 设备。

构建线程 HAL 服务

在 Cuttlefish 中试用线程

Cuttlefish 是虚拟 Android 设备。在开始构建自己的 HAL 服务之前,最好先在 Cuttlefish 中尝试使用线程,以了解 HAL 的运作方式。

Cuttlefish 中提供了默认的 Thread HAL 服务,该服务使用模拟的 RCP 实现,可通过 UDP 套接字在模拟的 Thread (802.15.4) 无线电之间收发数据包。

在 Cuttlefish 实例中,系统会预安装“ThreadNetworkDemoApp”。打开该应用,将 Cuttlefish 设备加入默认的 Thread 网络。

demoapp-screenshot

我们还提供了 ot-ctlot-cli-ftd 命令行工具,用于配置 Thread 网络以进行测试。这些工具支持您可能已经熟悉的所有 OpenThread CLI 命令。

您可以通过以下方式 grep 查找 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 线程 HAL 服务使用默认线程 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

与网络共享 Mainline 模块中的线程堆栈类似,Cuttlefish 中的默认线程 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,则线程 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 规则

默认情况下,线程 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;

整合

现在,您已经完成了添加线程所需的几乎所有代码,最后一步是将线程 HAL APEX 和 sepolicy 规则添加到设备的映像中。

为此,您可以将以下代码添加到设备的 Makefile(例如 device.mk)中:

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

如果一切正常,您现在应该会看到类似于以下内容的线程 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

自定义

线程主线程模块(实际上是“网络共享”模块的一部分)提供了一些可叠加的配置,供应商可以指定这些配置以自定义堆栈行为。如需查看完整列表,请参阅 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 边界路由器通告。

如需添加叠加层,您需要为 Orange 设备创建新的 ConnectivityOverlayOrangeruntime_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 测试可确保线程 HAL 服务在设备上按预期运行。您可以使用以下命令运行测试

    atest VtsHalThreadNetworkTargetTest

  • CTS 测试可确保线程 API 在您的设备上按预期运行。您可以使用以下命令运行测试

    atest CtsThreadNetworkTestCases

  • 集成测试可更好地保证线程 Mainline 代码在设备上的运行方式。您可以使用以下命令运行测试

    atest ThreadNetworkIntegrationTests

您还可以找到有关如何使用这些已发布的测试套件运行 VTS/CTS/MTS 测试的更多说明:

使用 Thread 演示应用进行测试

与 Cuttlefish 设备类似,您可以将 Thread 演示版应用添加到系统映像中:

# ThreadNetworkDemoApp for testing
PRODUCT_PACKAGES_DEBUG += ThreadNetworkDemoApp

请注意,您应仅将其添加到调试 / eng 变体(例如 PRODUCT_PACKAGES_DEBUG),因为此类资源不应包含在面向最终用户的用户 build 中。