1. Прежде чем начать
В нашей кодовой лаборатории Thread Border Router (TBR) мы покажем, как создать Thread Border Router на базе Raspberry Pi. В этой кодовой лаборатории мы
- Установите двунаправленное IP-соединение между сетями Thread и Wi-Fi/Ethernet.
- Обеспечьте обнаружение двунаправленной службы через mDNS (по каналу Wi-Fi/Ethernet) и SRP (в сети Thread).
Эта лаборатория кода основана на предыдущей и описывает, как ваш собственный пограничный маршрутизатор и ваше приложение могут взаимодействовать с API Google для создания однопоточной сети. Объединение учетных данных Thread важно, поскольку оно повышает надежность сети и упрощает взаимодействие пользователей с приложениями, использующими Thread.
Предварительные условия
- Завершите кодовую лабораторию OTBR
- Базовые знания Linux, Android/Kotlin и сетей Thread.
Что вы узнаете
- Как использовать 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 для получения учетных данных потока, либо на запрос POST для установки учетных данных потока. Полезная нагрузка всегда представляет собой структуру JSON с TLV.
Следующий запрос PUT устанавливает новые учетные данные ожидающего потока в 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. Собственный HTTP Rest API агента OTBR.
Пограничный маршрутизатор OpenThread по умолчанию строится с флагом REST_API=1
, который включает REST API. Если ваша сборка из предыдущей кодовой лаборатории не включала REST API, обязательно создайте 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 на Android позволяют и ожидают регистрацию учетных данных для всех TBR в вашей сети. Каждый из них идентифицируется своим идентификатором агента пограничного маршрутизатора (BAID). Для выполнения этой задачи вы будете использовать метод addCredentials()
интерфейса ThreadNetworkClient
. Первый TBR, добавляемый в хранилище Сервисов Google Play, определяет предпочтительные учетные данные для этого мобильного устройства.
Приложение, которое добавляет набор сетевых учетных данных Thread к своему BAID, становится владельцем учетных данных и имеет полные разрешения на доступ к ним. Если вы попытаетесь получить доступ к учетным данным, добавленным другими приложениями, вы получите ошибку PERMISSION_DENIED. Однако предпочтительные учетные данные всегда доступны для любого приложения с согласия пользователя. Мы рекомендуем обновлять учетные данные, хранящиеся в сервисах Google Play, при обновлении сети Thread Border Router. Хотя эта информация сегодня не используется, мы можем предоставить расширенные возможности поездок в будущем.
Даже если первый TBR позже будет исключен, предпочтительные учетные данные сохранятся на устройстве Android. После установки другие приложения, которые управляют учетными данными потока, могут получать учетные данные из вызова getPreferredCredentials()
.
Google TBR Синхронизация
Устройства Android автоматически синхронизируются с Google TBR. Если на Android нет учетных данных, устройства извлекают их из Google TBR в вашей сети, и эти учетные данные становятся предпочтительными учетными данными. Синхронизация между TBR и устройством Android происходит только в том случае, если TBR сопряжен с одним пользователем или с двумя пользователями, которые находятся в одном Умном доме ( Структура ).
Этот процесс также произойдет, когда другой пользователь Google находится в GHA для Android или GHA для iOS, когда пользователь находится в той же структуре. В случае GHA для iOS предпочтительные учетные данные устанавливаются в хранилище iOS, если предпочтительные учетные данные не существуют.
Если в одной сети существуют два устройства Android (или Android + iGHA) с разными наборами предпочтительных учетных данных, то устройство, которое первоначально настроило TBR, будет преобладать в TBR.
Подключение сторонней TBR
Хранилище учетных данных в настоящее время не ограничено Умным домом пользователя ( Структура ). Каждое устройство Android будет иметь свое хранилище BAID, но как только в сети появится Google TBR, другие устройства Android и iOS, на которых работает приложение Google Home для iOS, будут синхронизироваться с этим TBR и пытаться установить локальные учетные данные в памяти телефона.
Прежде чем новый OOB TBR создаст сеть, важно проверить, существует ли уже предпочтительная сеть в хранилище Android.
- Если предпочтительная сеть существует, поставщик должен ее использовать. Это гарантирует, что устройства Thread будут подключены к одной сети Thread, когда это возможно.
- Если предпочитаемой сети не существует, создайте новый набор учетных данных и назначьте его своему TBR в Сервисах Google Play. Android будет учитывать эти учетные данные как стандартные учетные данные, установленные на всех TBR на базе Google, а другие поставщики смогут расширить охват и надежность вашей сети с помощью дополнительных устройств.
4. Клонирование и изменение вашего приложения для Android
Мы создали приложение для Android, которое демонстрирует основные возможные вызовы API Thread. Вы можете использовать эти шаблоны в своем приложении. В этой лаборатории кода мы клонируем пример приложения Google Home для Matter с Github.
Весь показанный здесь исходный код уже закодирован в примере приложения. Вам предлагается изменить его в соответствии со своими потребностями, но вы можете просто клонировать приложение или запустить предварительно созданные двоичные файлы, чтобы проверить функциональность.
- Клонируйте его, используя:
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
- Загрузите и откройте Android Studio .
- Нажмите «Файл» > «Открыть» и укажите свой клонированный репозиторий.
- Включите режим разработчика на своем телефоне Android.
- Подключите его к компьютеру через USB-кабель.
- Запустите приложение из Android Studio через <Cmd+R> (OS X) или <Ctrl+R> (Win, Linux).
- Перейдите в Колесо -> Утилиты разработчика -> Сеть потоков.
- Взаимодействуйте с различными доступными опциями. В разделах ниже мы распакуем код, который выполняется на каждой кнопке.
Существуют ли предпочтительные учетные данные?
Первый вопрос, который производитель 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
вы хотите создать и запустить намерение, используя этот результат от отправителя.
В нашем коде для целей организации/архитектуры мы используем MutableLiveData<IntentSender?>
поскольку исходный код находится в ViewModel (ThreadViewModel.kt), а наблюдатели намерений находятся во фрагменте активности ( 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. Ваше приложение будет единственным, кто сможет считывать учетные данные, связанные с идентификатором пограничного агента вашего 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
Эта часть является собственностью каждого поставщика, и в этой лаборатории мы реализуем ее либо через HTTP-сервер DBUS+Python, либо через собственный HTTP-сервер отдыха от 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 вам потребуется внести изменения в манифест, сборку и импорт для поддержки модуля потоков сервисов 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>
Сборка.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 для создания списка доступных пограничных маршрутизаторов потоков в сети, а также их соответствующих BAID.
Это очень полезно при вводе информации о вашем TBR в хранилище учетных данных GPS. Однако его использование выходит за рамки данной лаборатории. Мы используем библиотеку Android Service Discovery NSDManager , а полный исходный код доступен в примере приложения в ServiceDiscovery.kt
.
6. Собираем все вместе
После того как вы реализовали эти вызовы или использовали образец приложения, вы можете полностью подключить свой RPi OTBR. В нашем примере приложения имеется 8 кнопок:
Возможная последовательность подключения вашего TBR:
- Запросить , существуют ли льготные учетные данные (синий, 1-я строка)
- В зависимости от ответа
- Получите предпочтительные учетные данные GPS (синий, 2-й ряд)
- Установите учетные данные TBR в GPS (синий, 3-й ряд) -> Выберите свой TBR -> Создать случайный -> Введите имя сети -> Ок.
- Теперь, когда у вас есть предпочтительные учетные данные, установите их в свой OTBR с помощью команды Set RPi OTBR Credentials , которая применит эти учетные данные к ожидающему набору.
По умолчанию для примера приложения используется задержка в 10 секунд. Таким образом, по истечении этого периода учетные данные вашего RPi TBR (и других узлов, которые могут существовать в его сети) будут перенесены в новый набор данных.
7. Заключение
В этой лаборатории кода мы клонировали пример приложения Android и проанализировали несколько фрагментов кода, в которых используются API-интерфейсы хранилища потоков сервисов Google Play. Мы использовали эти API, чтобы иметь общий набор данных, который мы можем внедрить в TBR RPi, который демонстрирует TBR поставщика.
Наличие всех TBR пользователя в одной сети повышает устойчивость и охват сети потоков. Это также предотвращает ошибочные действия пользователя, когда приложения не могут подключиться к устройствам Thread, поскольку у них нет доступа к учетным данным.
Мы надеемся, что эта кодовая лаборатория и примеры приложений помогут вам спроектировать и разработать собственное приложение и продукт Thread Border Router.
8. Ссылки
RCP-сопроцессор
ДБУС