פיתוח נתב גבולות ל-Android

הצגת המקור ב-Android Code Search

אם אתם לא יצרנים של מכשירי Android או צ'יפים של Thread, תוכלו להפסיק לקרוא כאן.

במסמך הזה מוסבר איך ליצור מכשיר חדש מסוג Thread Border Router שמבוסס על Android עם קוד המקור העדכני ביותר של AOSP. במסמך הזה תלמדו:

  1. הארכיטקטורה והסטטוס הכללי של התמיכה ב-Thread ב-Android
  2. איך יוצרים שירות HAL משלכם ל-Thread
  3. איך מוודאים שהמכשיר תואם ל-Google Home
  4. איך בודקים את נתב הגבולות לפרוטוקול Thread

אם אתם צריכים תמיכה, תוכלו לדווח על בעיה ב-GitHub או לפתוח דיון אם יש לכם שאלות.

סקירה כללית

סטאק ה-Thread של Android מבוסס על OpenThread ו-ot-br-posix, ש-Google פרסמה כקוד פתוח ב-GitHub. בדומה ל-OpenThread שמפותח במאגר GitHub ציבורי, גם סטאק Android Thread מפותח בקוד המקור הציבורי של AOSP. כל התכונות ותיקוני הבאגים נשלחים קודם ל-AOSP. כך הספקים יכולים להתחיל להשתמש בגרסאות העדכניות ביותר של Thread בלי להמתין להשקות הרגילות של Android.

ארכיטקטורה

סטאק ה-Thread של Android כולו מורכב משני רכיבים עיקריים: סטאק הליבה של Thread במחיצה מערכתית רגילה ושירות Thread HAL במחיצה של הספק. בדרך כלל, ספקי המכשירים צריכים רק לדאוג ולפתח את שירות ה-HAL.

android-thread-arch

לפניכם סיכום קצר של אופן הפעולה של סטאק Thread ב-Android: - יש שירות מערכת של Java Thread בשרת המערכת שמנהל את כל הסטאק – מספק את ה-API של מערכת Thread, יוצר את ממשק המנהרה thread-wpan, רושם את רשת Thread בשירות הקישוריות ומטמיע את הפונקציונליות של Border Routing ו-Advertising Proxy. - סטאק הליבה של Thread או OpenThread מתארח בתהליך מקורי עצמאי ללא הרשאות שנקרא ot-daemon. ot-daemon מנוהל ישירות על ידי שירות המערכת של Java דרך ממשקי API פרטיים של AIDL, והוא ניגש לרדיו החומרה של Thread דרך Thread HAL API. - שירות Thread HAL שספק מסוים מספק חייב ליישם את Thread HAL API. בדרך כלל הוא פועל כ-RCP ומטמיע את פרוטוקול spinel.

איפה נמצא הקוד?

הגדרת סביבת הפיתוח

ספקי מכשירי Android שכבר יצרו סביבה לפיתוח Android למכשיר יכולים לדלג על הקטע הזה.

אם אתם חדשים בסביבת Android או אם אתם ספקי סיליקון שרוצים להפוך את צ'יפ Thread שלכם לתואם ל-Android ולספק תמיכה לספקי מכשירים, כדאי להמשיך לקרוא.

הדרכה ב-Codelab למפתחי Android

כדי להגדיר את סביבת הפיתוח של Android בפעם הראשונה, תוכלו להיעזר בקודלאב הבא: https://source.android.com/docs/setup/start. בסיום הקודלאב תוכלו ליצור מכשיר Cuttlefish מדומה ולהריץ אותו מקוד מקור.

פיתוח שירות HAL של Thread

איך משתמשים בשרשור ב-Cuttlefish

Cuttlefish הוא מכשיר Android וירטואלי. לפני שמתחילים לפתח שירות HAL משלכם, מומלץ לנסות את Thread ב-Cuttlefish כדי להבין איך HAL פועל.

שירות HAL של Thread שמוגדר כברירת מחדל מסופק ב-Cuttlefish, והוא מיושם באמצעות RCP מדומה שמעביר חבילות דרך שקע UDP אל רדיו Thread (802.15.4) מדומה וממנו.

במכונה של Cuttlefish, אפליקציית ThreadNetworkDemoApp מותקנת מראש. פותחים את האפליקציה הזו כדי להצטרף עם מכשיר Cuttlefish לרשת Thread שמוגדרת כברירת מחדל.

demoapp-screenshot

יש גם את הכלים של שורת הפקודה ot-ctl ו-ot-cli-ftd, שבעזרתם אפשר להגדיר את רשת ה-Thread לבדיקה. הכלים האלה תומכים בכל הפקודות של OpenThread CLI שכבר מוכרות לכם.

כדי לחפש יומנים של שירות HAL של Cuttlefish Thread:

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 שמוגדר כברירת מחדל, יחד עם קובץ ה-RCP הבינארי המדומה של OpenThread. בחלק הבא מוסבר איך זה עובד.

שירות HAL שמוגדר כברירת מחדל

שירות HAL שמוגדר כברירת מחדל כלול יחד עם Thread HAL API. שירות HAL שמוגדר כברירת מחדל תומך גם במכשירי RCP מדומים וגם במכשירי RCP אמיתיים. הוא מקבל כתובת URL אופציונלית של מכשיר RCP, ואם לא מציינים כתובת URL, ברירת המחדל היא מכשיר ה-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 של הספק

בדומה ל-Thread stack במודול הראשי של Tethering, גם שירות ה-HAL של Thread שמוגדר כברירת מחדל ב-Cuttlefish ארוז במודול 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 כדי לגשת למכשיר ה-RCP של החומרה.

  • binaries: הקובץ הבינארי שנשלח במודול APEX הזה

  • threadnetwork-service.rc: האופן שבו שירות HAL יופעל. צריך לציין כאן את הנתיב של מכשיר ה-RCP.

  • android.hardware.thread_network.prebuilt.xml: מגדיר את תכונת החומרה android.hardware.thread_network. הפעולה הזו נדרשת כדי שמערכת Android תדע שיש למכשיר תמיכה בחומרה של Thread. אחרת, סטאק ה-Thread של Android לא יופעל.

יצירת שירות HAL

מפתחים של מכשירי Android או ספקי סיליקון צריכים לדעת איך ליצור קושחת OT RCP עבור צ'יפ Thread. ההנחיות הבאות מבוססות על ההנחה ששבב החומרה מחובר בצורה נכונה ותקין.

הדרך הפשוטה ביותר ליצור HAL APEX היא ליצור APEX חדש עם הקבצים הבינאריים וה-prebuilts של HAL APEX שמוגדרים כברירת מחדל. לדוגמה, אם שם החברה הוא Banana ומכשיר ה-RCP במכשיר הוא /dev/ttyACM0, ה-HAL APEX של Thread ייראה כך:

  • 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

כברירת מחדל, לשירות Thread HAL אין גישה למכשיר ה-RCP (לדוגמה /dev/ttyACM0). צריך להוסיף כללי מדיניות אבטחה מותאמים אישית לספרייה 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

התאמה אישית

מודול הליבה של Thread (הוא למעשה חלק מהמודול 'קישור לרשת אחרת') מספק כמה הגדרות שכבת-על שספקים יכולים לציין כדי להתאים אישית את התנהגות הסטאק. הרשימה המלאה מופיעה בקובץ config_thread.xml.

בדרך כלל, צריך להגדיר את config_thread_border_router_default_enabled לערך true כדי להפעיל את המכשיר כנתב גבולות לפרוטוקול Thread, ולשנות את הערכים של config_thread_vendor_name, ‏ config_thread_vendor_oui ו-config_thread_model_name לערכי הספק או המוצר. הערכים האלה ייכללו בשירות ה-mDNS‏ _meshcop._udp, שתמיד מפורסם על ידי Thread Border Router.

כדי להוסיף את שכבת-העל, צריך ליצור יעד חדש מסוג ConnectivityOverlayOrange runtime_resource_overlay למכשיר של Orange. יוצרים ספרייה חדשה בשם ConnectivityOverlay/ בקטע device/banana/orange/rro_overlays ויוצרים בה את התוכן הבא:

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 מוודאת שממשקי ה-API של Thread פועלים כצפוי במכשיר. אפשר להריץ את הבדיקות באמצעות הפקודה

    atest CtsThreadNetworkTestCases

  • בדיקת השילוב מספקת ערובה נוספת לאיכות הקוד של Thread במכשיר. אפשר להריץ את הבדיקות באמצעות הפקודה

    atest ThreadNetworkIntegrationTests

יש גם הוראות נוספות להרצת בדיקות VTS/‏CTS/‏MTS באמצעות חבילות הבדיקה האלה:

בדיקה באמצעות אפליקציית הדגמה של Thread

בדומה למכשיר Cuttlefish, אפשר להוסיף את אפליקציית הדגמה של Thread לתמונת המערכת:

# ThreadNetworkDemoApp for testing
PRODUCT_PACKAGES_DEBUG += ThreadNetworkDemoApp

חשוב לזכור שצריך להוסיף אותו רק לגרסה לניפוי באגים או לגרסה ל-eng (לדוגמה, PRODUCT_PACKAGES_DEBUG), כי הוא לא אמור להיכלל בגרסה של המשתמש לצרכן הקצה.