1. לפני שמתחילים
ב-Codelab שלנו בנושא נתב גבולות עם פרוטוקול Thread (TBR), אנחנו מסבירים איך לבנות נתב גבולות עם פרוטוקול Thread על בסיס Raspberry Pi. ב-Codelab הזה
- יצירת קישוריות IP דו-כיוונית בין רשתות Thread ו-Wi-Fi/Ethernet.
- זיהוי שירותים דו-כיווני באמצעות mDNS (בקישור Wi-Fi/Ethernet) ו-SRP (ברשת Thread).
ה-codelab הזה מבוסס על ה-codelab הקודם, ומסביר איך נתב הגבול שלכם והאפליקציה שלכם יכולים ליצור אינטראקציה עם ממשקי Google API כדי ליצור רשת Thread אחת. חשוב לאחד את פרטי הכניסה של Thread כי זה משפר את החוסן של הרשת ומפשט את האינטראקציות של המשתמשים עם האפליקציות שמסתמכות על Thread.
דרישות מוקדמות
- משלימים את ה-Codelab בנושא OTBR
- ידע בסיסי ב-Linux, ב-Android/Kotlin וברשתות Thread
מה תלמדו
- איך משתמשים בממשקי Thread Sharing API כדי לקבל ולהגדיר קבוצות של פרטי כניסה
- איך מגדירים נתב גבול OpenThread משלכם עם אותם פרטי כניסה כמו ברשת של Google
הדרישות
- לוח Raspberry Pi 4 או לוח אחר מבוסס-Linux שמופעל בו Open Thread Border Router (OTBR)
- לוח שמספק קישוריות IEEE 802.15.4 כמעבד משותף של רדיו (RCP). רשימה של מאגרי מידע של ספקי SoC שונים וההוראות שלהם מופיעה בדף OpenThread ב-GitHub
2. הגדרת שירות HTTP
אבן הבניין הראשונה שאנו צריכים היא ממשק שיאפשר לנו לקרוא את פרטי הכניסה הפעילים ולכתוב את פרטי הכניסה בהמתנה ב-OTBR שלכם. כשיוצרים TBR, משתמשים במנגנונים קנייניים משלכם, כמו בדוגמאות הבאות. האפשרות הראשונה מראה איך ליצור אינטראקציה עם סוכן OTBR באופן מקומי דרך DBUS, והאפשרות השנייה מראה איך להשתמש ב-Rest API שאפשר לבנות על OTBR.
אף אחת מהשיטות לא מאובטחת, ואסור להשתמש בהן כמו שהן בסביבת ייצור. עם זאת, ספק יכול ליצור הצפנה סביב כל אחת מהשיטות האלה כדי להשתמש בה בסביבת ייצור, או שאתם יכולים להרחיב את שירות הניטור שלכם כדי להנפיק קריאות חוזרות של HTTP או קריאות DBUS מקומיות.
אפשרות 1: DBUS ו-HTTP API בסקריפט Python

בשלב הזה נוצר שירות HTTP בסיסי שחושף שתי נקודות קצה לקריאה ולהגדרת פרטי כניסה, ובסופו של דבר קורא לפקודות DBUS.
ב-RPi שישמש כ-OTBR, מתקינים את יחסי התלות של Python 3:
$ pip install dbus-python shlex json
מריצים את הסקריפט כך:
$ sudo python credentials_server.py 8081 serving at port 8081
הדוגמה מגדירה שרת HTTP ביציאה 8081 ומאזינה בנתיב הבסיסי לבקשת GET כדי לאחזר את פרטי הכניסה של Thread, או לבקשת POST כדי להגדיר את פרטי הכניסה של Thread. המטען הייעודי הוא תמיד מבנה JSON עם TLV.
בקשת PUT הבאה מגדירה פרטי כניסה חדשים של Thread בהמתנה ל-OTBR באמצעות הנתיב /node/dataset/pending. במקרה כזה, פרטי הכניסה שממתינים לאישור יחולו תוך 10 שניות:
PUT /node/dataset/pending
Host: <IP>:8081
ContentType: "application/json"
acceptMimeType: "application/json"
...
{
"ActiveDataset": "<TLV encoded new Thread Dataset>"
"PendingTimestamp": {
"Seconds": <Unix timestamp in seconds>,
"Ticks": 0,
"Authoritative": false
},
"Delay": 10000 // in milliseconds
}
בקשת GET אל /node/dataset/active מאחזרת את פרטי הכניסה הפעילים כרגע.
GET /node/dataset/active Host: <IP>:8081 ContentType = "application/json" acceptMimeType = "text/plain" ... <TLV encoded Thread Dataset>
הסקריפט קורא לפקודות DBUS R/W לנתיב האוטובוס io.openthread.BorderRouter.wpan0, נתיב האובייקט /io/openthread/BorderRouter/wpan0:
# D-BUS interface
def call_dbus_method(interface, method_name, *arguments):
bus = dbus.SystemBus()
obj = bus.get_object('io.openthread.BorderRouter.wpan0', '/io/openthread/BorderRouter/wpan0')
iface = dbus.Interface(obj, interface)
method = getattr(iface, method_name)
res = method(*arguments)
return res
def get_dbus_property(property_name):
return call_dbus_method('org.freedesktop.DBus.Properties', 'Get', 'io.openthread.BorderRouter',
property_name)
def set_dbus_property(property_name, property_value):
return call_dbus_method('org.freedesktop.DBus.Properties', 'Set', 'io.openthread.BorderRouter',
property_name, property_value)
DBUS מאפשרת בדיקה של היכולות שלה. אפשר לעשות את זה כך:
$ sudo dbus-send --system --dest=io.openthread.BorderRouter.wpan0 \
--type=method_call --print-reply /io/openthread/BorderRouter/wpan0 \
org.freedesktop.DBus.Introspectable.Introspect
אפשר גם לעיין ביכולות הנתמכות שמתועדות כאן.
אפשרות 2: OTBR Agent native HTTP Rest API

ה-OpenThread Border Router נוצר כברירת מחדל עם הדגל REST_API=1, שמפעיל את ה-API בארכיטקטורת REST. אם ה-build שלכם מ-Codelab קודם לא הפעיל את API בארכיטקטורת REST, חשוב לוודא שאתם מבצעים build של OTBR ב-RPi עם הדגל הזה:
$ REST_API=1 INFRA_IF_NAME=wlan0 ./script/setup
אפשר להפעיל מחדש סוכן OTBR על ידי הרצת הפקודה:
$ sudo systemctl restart otbr-agent.service
הסוכן מפעיל שרת HTTP ביציאה 8081. השרת הזה מאפשר למשתמש או לתוכנית ניטור לבצע משימות רבות ב-OTBR (מתועד כאן). אפשר להשתמש בדפדפן, ב-curl או ב-wget כדי לבדוק את התוכן שלו. בין הנתיבים הרבים שנתמכים נמצאים תרחישי השימוש שמתוארים למעלה, עם הפועל GET ב-/node/dataset/active והפועל PUT ב-/node/dataset/pending.
3. הגדרה של Credential Framework ב-Android
פרטי כניסה מועדפים
Google Play Services ב-Android מאפשרת ומצפה לרישום של פרטי הכניסה לכל ה-TBR ברשת שלכם. כל אחד מהם מזוהה באמצעות מזהה סוכן נתב הגבול (BAID). כדי לבצע את המשימה הזו, תשתמשו בשיטה addCredentials() של הממשק ThreadNetworkClient. ה-TBR הראשון שמוסיפים לאחסון של Google Play Services קובע את פרטי הכניסה המועדפים למכשיר הנייד הזה.
האפליקציה שמוסיפה קבוצה של פרטי כניסה לרשת Thread ל-BAID שלה הופכת לבעלים של פרטי הכניסה, ויש לה הרשאות מלאות לגשת אליהם. אם תנסו לגשת לפרטי כניסה שנוספו על ידי אפליקציות אחרות, תקבלו את השגיאה PERMISSION_DENIED. עם זאת, פרטי הכניסה המועדפים תמיד זמינים לכל אפליקציה לאחר הסכמה מהמשתמש. מומלץ לעדכן את פרטי הכניסה שמאוחסנים ב-Google Play Services כשמעדכנים את רשת נתב הגבול של פרוטוקול Thread. אנחנו לא משתמשים במידע הזה כרגע, אבל יכול להיות שבעתיד נספק מסלולים משופרים.
גם אם בהמשך תבחרו להחריג את ה-TBR הראשון, פרטי הכניסה המועדפים יישארו במכשיר Android. אחרי ההגדרה, אפליקציות אחרות שמנהלות פרטי כניסה לפרוטוקול Thread יכולות לקבל את פרטי הכניסה באמצעות קריאה ל-getPreferredCredentials().
Google TBR Sync
במכשירי Android, הסנכרון עם TBR של Google מתבצע אוטומטית. אם אין אישורים ב-Android, המכשירים מחלצים אותם מ-TBR של Google ברשת שלכם, והאישורים האלה הופכים לאישורים המועדפים. הסנכרון בין מכשירי TBR למכשיר Android מתבצע רק אם מכשיר ה-TBR משויך למשתמש יחיד, או אם הוא משויך לשני משתמשים ששייכים לאותו בית חכם (מבנה).
התהליך הזה יקרה גם כשמשתמש אחר ב-Google נמצא ב-GHA ל-Android או ב-GHA ל-iOS, כשהמשתמש נמצא באותו מבנה. במקרה של GHA ל-iOS, אם אין פרטי כניסה מועדפים, הם מוגדרים באחסון של iOS.
אם קיימים שני מכשירי Android (או Android + iGHA) באותה רשת עם קבוצות שונות של פרטי כניסה מועדפים, המכשיר שבו הוגדר במקור ה-TBR יקבל עדיפות ב-TBR.
הטמעה של TBR של צד שלישי
האחסון של פרטי הכניסה לא מוגבל כרגע לבית החכם של המשתמש (מבנה). לכל מכשיר Android יהיה אחסון BAID משלו, אבל ברגע שיהיה TBR של Google ברשת, מכשירי Android אחרים ומכשירי iOS שמופעלת בהם אפליקציית Google Home ל-iOS יסתנכרנו עם ה-TBR הזה וינסו להגדיר אישורים מקומיים באחסון הטלפון.
לפני ש-OOB TBR חדש יוצר רשת, חשוב לבדוק אם רשת מועדפת כבר קיימת באחסון של Android.
- אם קיימת רשת מועדפת, הספק צריך להשתמש בה. כך אפשר לוודא שמכשירי Thread מחוברים לרשת Thread אחת, אם אפשר.
- אם אין רשת מועדפת, צריך ליצור קבוצת פרטי כניסה חדשה ולהקצות אותה ל-TBR ב-Google Play Services. מערכת Android תכבד את פרטי הכניסה האלה כפרטי הכניסה הסטנדרטיים שמוגדרים בכל ה-TBR מבוססי Google, וספקים אחרים יוכלו לשפר את טווח הרשת ואת החוסן שלה באמצעות מכשירים נוספים.

4. שיבוט ושינוי של אפליקציית Android
יצרנו אפליקציית Android שמציגה את ההפעלות העיקריות האפשריות של Thread API. אפשר להשתמש בדפוסים האלה באפליקציה. במעבדת התכנות הזו נשכפל את אפליקציית הדוגמה של Google Home לתקן Matter מ-GitHub.
כל קוד המקור שמוצג כאן כבר מקודד באפליקציה לדוגמה. אתם מוזמנים לשנות אותו בהתאם לצרכים שלכם, אבל אתם יכולים פשוט לשכפל את האפליקציה או להריץ את הקבצים הבינאריים המובנים מראש כדי לבדוק את הפונקציונליות.
- משכפלים אותו באמצעות:
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
- מורידים ופותחים את Android Studio.
- לוחצים על File (קובץ) > Open (פתיחה) ומצביעים על המאגר המשוכפל.
- מפעילים את מצב הפיתוח בטלפון Android.
- מחברים אותו למחשב באמצעות כבל USB.
- מריצים את האפליקציה מ-Android Studio באמצעות <Cmd+R> (OS X) או <Ctrl+R> (Win, Linux)
- עוברים אל סמל גלגל השיניים -> כלי עזר למפתחים -> רשת פרוטוקול Thread
- אפשר לנסות את האפשרויות השונות שזמינות. בקטעים הבאים נסביר את הקוד שמופעל בכל לחצן.
האם קיימים פרטי כניסה מועדפים?
השאלה הראשונה שיצרן של מכשיר TBR צריך לשאול את Google היא אם כבר קיימת במכשיר קבוצה מועדפת של פרטי כניסה. זו צריכה להיות נקודת ההתחלה של התהליך. הקוד שבהמשך מבצע שאילתה ב-GPS לגבי קיומם של פרטי כניסה. לא מוצגת בקשת הסכמה מהמשתמש כי לא משותפים פרטי כניסה.
/**
* Prompts whether credentials exist in storage or not. Consent from user is not necessary
*/
fun doGPSPreferredCredsExist(activity: FragmentActivity) {
try {
// Uses the ThreadNetwork interface for the preferred credentials, adding
// a listener that will receive an intentSenderResult. If that is NULL,
// preferred credentials don't exist. If that isn't NULL, they exist.
// In this case we'll not use it.
ThreadNetwork.getClient(activity).preferredCredentials.addOnSuccessListener { intentSenderResult ->
intentSenderResult.intentSender?.let { intentSender ->
ToastTimber.d("threadClient: preferred credentials exist", activity)
// don't post the intent on `threadClientIntentSender` as we do when
// we really want to know which are the credentials. That will prompt a
// user consent. In this case we just want to know whether they exist
} ?: ToastTimber.d(
"threadClient: no preferred credentials found, or no thread module found", activity
)
}.addOnFailureListener { e: Exception ->
Timber.d("ERROR: [${e}]")
}
} catch (e: Exception) {
ToastTimber.e("Error $e", activity)
}
}
קבלת פרטי כניסה מועדפים ל-GPS
אם הם קיימים, צריך לקרוא את פרטי הכניסה. ההבדל היחיד מהקוד הקודם הוא שאחרי קבלת intentSenderResult, רוצים ליצור ולהפעיל intent באמצעות התוצאה הזו מהשולח.
בקוד שלנו, אנחנו משתמשים ב-MutableLiveData<IntentSender?> למטרות ארגון או ארכיטקטורה, כי הקוד המקורי נמצא ב-ViewModel (ThreadViewModel.kt) וה-observers של הכוונות נמצאים ב-Activity Fragment (ThreadFragment.kt). לכן, אחרי שה-intentSenderResult יפורסם בנתונים הפעילים, נבצע את התוכן של האובייקט הזה:
viewModel.threadClientIntentSender.observe(viewLifecycleOwner) { sender ->
Timber.d(
"threadClient: intent observe is called with [${intentSenderToString(sender)}]"
)
if (sender != null) {
Timber.d("threadClient: Launch GPS activity to get ThreadClient")
threadClientLauncher.launch(IntentSenderRequest.Builder(sender).build())
viewModel.consumeThreadClientIntentSender()
}
}
הפעולה הזו תפעיל בקשת הסכמה מהמשתמש לשיתוף פרטי הכניסה, ואם הבקשה תאושר, התוכן יוחזר באמצעות:
threadClientLauncher =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
if (result.resultCode == RESULT_OK) {
val threadNetworkCredentials =
ThreadNetworkCredentials.fromIntentSenderResultData(result.data!!)
viewModel.threadPreferredCredentialsOperationalDataset.postValue(
threadNetworkCredentials
)
} else {
val error = "User denied request."
Timber.d(error)
updateThreadInfo(null, "")
}
}
הסבר על פרסום פרטי הכניסה ב-MutableLiveData<ThreadNetworkCredentials?> מופיע בהמשך.
הגדרת פרטי כניסה ל-GPS
בין אם הם קיימים ובין אם לא, צריך לרשום את ה-TBR ב-Google Play Services. האפליקציה שלכם תהיה היחידה שתוכל לקרוא את פרטי הכניסה שמשויכים למזהה של סוכן הגבולות ב-TBR, אבל אם ה-TBR שלכם הוא הראשון שנרשם, פרטי הכניסה האלה יועתקו לקבוצת פרטי הכניסה המועדפים. כל אפליקציה בטלפון יכולה לגשת למידע הזה, כל עוד המשתמש מאשר זאת.
/**
* Last step in setting the GPS thread credentials of a TBR
*/
private fun associateGPSThreadCredentialsToThreadBorderRouterAgent(
credentials: ThreadNetworkCredentials?,
activity: FragmentActivity,
threadBorderAgent: ThreadBorderAgent,
) {
credentials?.let {
ThreadNetwork.getClient(activity).addCredentials(threadBorderAgent, credentials)
.addOnSuccessListener {
ToastTimber.d("threadClient: Credentials added", activity)
}.addOnFailureListener { e: Exception ->
ToastTimber.e("threadClient: Error adding the new credentials: $e", activity)
}
}
}
הגדרת פרטי הכניסה למוצר TBR
החלק הזה הוא קנייני לכל ספק, וב-codelab הזה אנחנו מטמיעים אותו באמצעות DBUS+Python HTTP Rest Server או ה-HTTP Rest Server המקורי מ-OTBR.
/**
* Creates credentials in the format used by the OTBR HTTP server. See its documentation in
* https://github.com/openthread/ot-br-posix/blob/main/src/rest/openapi.yaml#L215
*/
fun createJsonCredentialsObject(newCredentials: ThreadNetworkCredentials): JSONObject {
val jsonTimestamp = JSONObject()
jsonTimestamp.put("Seconds", System.currentTimeMillis() / 1000)
jsonTimestamp.put("Ticks", 0)
jsonTimestamp.put("Authoritative", false)
val jsonQuery = JSONObject()
jsonQuery.put(
"ActiveDataset",
BaseEncoding.base16().encode(newCredentials.activeOperationalDataset)
)
jsonQuery.put("PendingTimestamp", jsonTimestamp)
// delay of committing the pending set into active set: 10000ms
jsonQuery.put("Delay", 10000)
Timber.d(jsonQuery.toString())
return jsonQuery
}
//(...)
var response = OtbrHttpClient.createJsonHttpRequest(
URL("http://$ipAddress:$otbrPort$otbrDatasetPendingEndpoint"),
activity,
OtbrHttpClient.Verbs.PUT,
jsonQuery.toString()
)
קבלת פרטי הכניסה ממוצר TBR
כמו שמוצג קודם, משתמשים בפועל GET HTTP כדי לקבל את פרטי הכניסה מה-TBR. סקריפט Python לדוגמה
יצירה וייבוא
כשיוצרים אפליקציית Android, צריך לבצע שינויים במניפסט, ב-build וב-imports כדי לתמוך במודול ה-thread של שירותי Google Play. שלושת הקטעים הבאים מסכמים את רוב התוספות.
שימו לב שאפליקציית הדוגמה שלנו מיועדת בעיקר להוספת מכשירים ל-Matter. לכן, קובצי ה-Manifest וה-Gradle שלה מורכבים יותר מהתוספות שנדרשות רק לשימוש בהרשאות לשרשור.
שינויים במניפסט
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
(...)
<!-- usesCleartextTraffic needed for OTBR local unencrypted communication -->
<!-- Not needed for Thread Module, only used for HTTP -->
<uses-feature
(...)
android:usesCleartextTraffic="true">
<application>
(...)
<!-- GPS automatically downloads scanner module when app is installed -->
<!-- Not needed for Thread Module, only used for scanning QR Codes -->
<meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="barcode_ui"/>
</application>
</manifest>
Build.gradle
// Thread Network implementation 'com.google.android.gms:play-services-threadnetwork:16.0.0' // Thread QR Code Scanning implementation 'com.google.android.gms:play-services-code-scanner:16.0.0' // Thread QR Code Generation implementation 'com.journeyapps:zxing-android-embedded:4.1.0' // Needed for using BaseEncoding class implementation 'com.google.guava:guava:31.1-jre'
ייבוא רלוונטי
// Thread Network Module import com.google.android.gms.threadnetwork.ThreadNetworkCredentials import com.google.android.gms.threadnetwork.ThreadBorderAgent import com.google.android.gms.threadnetwork.ThreadNetwork // Conversion of credentials to/fro Base16 (hex) import com.google.common.io.BaseEncoding // HTTP import java.io.BufferedInputStream import java.io.InputStream import java.net.HttpURLConnection import java.net.URL import java.nio.charset.StandardCharsets // Co-routines for HTTP calls import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch // JSON import org.json.JSONObject // Logs import timber.log.Timber // mDNS/SD import android.net.nsd.NsdServiceInfo // QR Code reader / writer import com.google.mlkit.vision.barcode.common.Barcode import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions import com.google.mlkit.vision.codescanner.GmsBarcodeScanning import com.google.zxing.BarcodeFormat import com.google.zxing.MultiFormatWriter import com.journeyapps.barcodescanner.BarcodeEncoder
5. גילוי mDNS/SD
אפליקציית הדוגמה שלנו משתמשת בגילוי mDNS/SD כדי ליצור רשימה של נתבי גבולות עם פרוטוקול Thread שזמינים ברשת, וגם את מזהי ה-BAID שלהם.
האפשרות הזו שימושית מאוד כשמזינים את המידע של ה-TBR באחסון של אישורי ה-GPS. עם זאת, השימוש בו לא נכלל בסדנת הקוד הזו. אנחנו משתמשים בספריית גילוי השירותים של Android NSDManager, וקוד המקור המלא זמין באפליקציה לדוגמה, ב-ServiceDiscovery.kt.
6. אז מה זה אומר?
אחרי שמטמיעים את הקריאות האלה או משתמשים באפליקציה לדוגמה, אפשר להשלים את ההגדרה של RPi OTBR. באפליקציית הדוגמה שלנו יש 8 לחצנים:

רצף אפשרי להטמעה של TBR:
- שאילתה לבדיקה אם קיימים פרטי כניסה מועדפים (כחול, שורה ראשונה)
- בהתאם לתשובה
- קבלת פרטי כניסה מועדפים ל-GPS (כחול, שורה שנייה)
- הגדרת פרטי כניסה ל-TBR ב-GPS (כחול, שורה שלישית) -> בחירת ה-TBR -> יצירת אקראי -> הזנת שם הרשת -> אישור
- אחרי שיש לכם פרטי כניסה מועדפים, אתם יכולים להגדיר אותם ב-OTBR באמצעות Set RPi OTBR credentials. כך פרטי הכניסה האלה יחולו על ההגדרה שממתינה.
באפליקציה לדוגמה, ברירת המחדל היא השהיה של 10 שניות. לכן, אחרי התקופה הזו, פרטי הכניסה של ה-RPi TBR (ושל צמתים אחרים שעשויים להיות ברשת שלו) יועברו למערך הנתונים החדש.
7. סיכום
ב-Codelab הזה, שיבטנו אפליקציית Android לדוגמה וניתחנו כמה קטעי קוד שמשתמשים בממשקי ה-API של Thread Storage בשירותי Google Play. השתמשנו בממשקי ה-API האלה כדי ליצור מערך נתונים משותף שאפשר להטמיע ב-TBR של RPi, שמציג את ה-TBR של ספק.
כשכל ה-TBR של משתמש נמצאים באותה רשת, העמידות והכיסוי של רשת Thread משתפרים. ההגדרה הזו גם מונעת תרחישי שימוש פגומים שבהם אפליקציות לא יכולות להפעיל מכשירי Thread כי אין להן גישה לפרטי הכניסה.
אנחנו מקווים שסדנת ה-codelab הזו ואפליקציות הדוגמה יעזרו לכם לעצב ולפתח אפליקציה משלכם ומוצר נתב גבול Thread.
8. קובצי עזר
מעבד משותף RCP
DBUS