הצגת המקור ב-Android Code Search
אם אתם לא יצרנים של מכשירי Android או צ'יפים של Thread, תוכלו להפסיק לקרוא כאן.
במסמך הזה מוסבר איך ליצור מכשיר חדש מסוג Thread Border Router שמבוסס על Android עם קוד המקור העדכני ביותר של AOSP. במסמך הזה תלמדו:
- הארכיטקטורה והסטטוס הכללי של התמיכה ב-Thread ב-Android
- איך יוצרים שירות HAL משלכם ל-Thread
- איך מוודאים שהמכשיר תואם ל-Google Home
- איך בודקים את נתב הגבולות לפרוטוקול Thread
אם אתם צריכים תמיכה, תוכלו לדווח על בעיה ב-GitHub או לפתוח דיון אם יש לכם שאלות.
סקירה כללית
סטאק ה-Thread של Android מבוסס על OpenThread ו-ot-br-posix
, ש-Google פרסמה כקוד פתוח ב-GitHub. בדומה ל-OpenThread שמפותח במאגר GitHub ציבורי, גם סטאק Android Thread מפותח בקוד המקור הציבורי של AOSP. כל התכונות ותיקוני הבאגים נשלחים קודם ל-AOSP. כך הספקים יכולים להתחיל להשתמש בגרסאות העדכניות ביותר של Thread בלי להמתין להשקות הרגילות של Android.
ארכיטקטורה
סטאק ה-Thread של Android כולו מורכב משני רכיבים עיקריים: סטאק הליבה של Thread במחיצה מערכתית רגילה ושירות Thread HAL במחיצה של הספק. בדרך כלל, ספקי המכשירים צריכים רק לדאוג ולפתח את שירות ה-HAL.
לפניכם סיכום קצר של אופן הפעולה של סטאק 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.
איפה נמצא הקוד?
- ה-API והשירות של Android Thread framework: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Connectivity/thread/
- Thread HAL API והטמעת שירות ברירת המחדל: https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/threadnetwork/
- המאגר של OpenThread שיובאו: https://cs.android.com/android/platform/superproject/main/+/main:external/openthread/
- המאגר שהובא של ot-br-posix: https://cs.android.com/android/platform/superproject/main/+/main:external/ot-br-posix/
הגדרת סביבת הפיתוח
ספקי מכשירי 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 שמוגדרת כברירת מחדל.
יש גם את הכלים של שורת הפקודה 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 באמצעות חבילות הבדיקה האלה:
- https://source.android.com/docs/core/tests/vts
- https://source.android.com/docs/compatibility/cts/run
- https://docs.partner.android.com/mainline/test/mts (כדי לגשת לקישור הזה צריך להיות שותף)
בדיקה באמצעות אפליקציית הדגמה של Thread
בדומה למכשיר Cuttlefish, אפשר להוסיף את אפליקציית הדגמה של Thread לתמונת המערכת:
# ThreadNetworkDemoApp for testing PRODUCT_PACKAGES_DEBUG += ThreadNetworkDemoApp
חשוב לזכור שצריך להוסיף אותו רק לגרסה לניפוי באגים או לגרסה ל-eng (לדוגמה, PRODUCT_PACKAGES_DEBUG
), כי הוא לא אמור להיכלל בגרסה של המשתמש לצרכן הקצה.