Thread Network SDK для Android

Thread Network SDK предоставляет функции, похожие на цифровую связку ключей, позволяя приложениям Android передавать учетные данные сети Thread сервисам Google Play. Это позволяет вашим приложениям настраивать любое устройство Thread из любой экосистемы умного дома, не раскрывая напрямую учетные данные и пользовательские данные.

С помощью всего нескольких вызовов API вы можете:

  1. Запросите у сервисов Google Play учетные данные предпочтительной сети Thread.
  2. Настройте новые устройства (Thread Border Router (TBR)) и добавьте учетные данные сети Thread в сервисы Google Play.
  3. Если у вас уже есть TBR, вы можете проверить, находятся ли TBRони в предпочтительной сети, и при необходимости перенести их.

Рассмотрим несколько сценариев для пользователей и разработчиков. В этом руководстве мы расскажем о большинстве из них, а также о других ключевых функциях и рекомендациях по использованию.

Основные термины и понятия API

Прежде чем начать, ознакомьтесь со следующими терминами:

  • Учетные данные сети Thread. Двоичный большой объект TLV-структур Thread, который кодирует название сети Thread, ключ сети и другие свойства, необходимые устройству Thread для подключения к определенной сети Thread.

  • Предпочитаемые учетные данные сети Thread. Автоматически выбранные учетные данные сети Thread, которыми можно делиться с приложениями разных поставщиков с помощью getPreferredCredentials API.

  • Идентификатор пограничного агента. Уникальный 16-байтовый идентификатор устройства. TBR Этот идентификатор создается и управляется поставщиками border router.

  • Приложение для настройки TBR. Это приложение для Android, которое настраивает новые устройства TBR и добавляет учетные данные сети Thread в сервисы Google Play. Ваше приложение является владельцем добавленных учетных данных и имеет к ним доступ.

Многие API Thread Network возвращают объект Task, который выполняется асинхронно. Чтобы зарегистрировать обратные вызовы для получения результата, можно использовать addOnSuccessListener и addOnFailureListener. Подробную информацию можно найти в документации по Task.

Владение учетными данными и их обслуживание

Приложение, которое добавляет учетные данные сети Thread, становится их владельцем и получает полный доступ к ним. Если вы попытаетесь получить доступ к учетным данным, добавленным другими приложениями, то получите ошибку PERMISSION_DENIED.

Как владельцу приложения, вам рекомендуется обновлять учетные данные, хранящиеся в сервисах Google Play, при обновлении сети TBR. Это означает, что вы должны добавлять учетные данные, когда это необходимо, обновлять их при изменении учетных данных сети Thread устройства "border router" и удалять их, когда устройство "TBR" удаляется или сбрасывается до заводских настроек.

Обнаружение пограничного агента

Учетные данные должны быть сохранены с идентификатором пограничного агента. Убедитесь, что приложение для настройки TBR может определить идентификаторы пограничных агентов ваших устройств TBR.

TBR должны использовать mDNS для передачи информации о сети Thread, включая название сети, расширенный идентификатор PAN и идентификатор пограничного агента. Соответствующие значения txt для этих атрибутов – nn, xp и id.

Для сетей с Google Thread Border Router (gTBR) сервисы Google Play автоматически получают учетные данные сети Google Thread.

Интегрируйте SDK в приложение для Android

Чтобы начать, выполните следующие действия:

  1. Следуйте инструкциям в статье Как настроить сервисы Google Play.

  2. Добавьте зависимость сервисов Google Play в файл build.gradle:

    implementation 'com.google.android.gms:play-services-threadnetwork:16.2.1'
    
  3. Необязательно. Определите класс данных BorderAgent для хранения информации TBR. Мы будем использовать эти данные в этом руководстве:

    data class BorderAgentInfo(
      // Network Name max 16 len
      val networkName: String = "",
      val extPanId: ByteArray = ByteArray(16),
      val borderAgentId: ByteArray = ByteArray(16),
      ...
    )
    

Далее мы расскажем, как добавлять предпочтительные учетные данные и управлять ими.

Новые настройки пограничного маршрутизатора Thread

Прежде чем создавать новую сеть для новых пограничных маршрутизаторов, попробуйте использовать учетные данные предпочтительной сети. Это позволяет подключать устройства Thread к одной сети Thread, когда это возможно.

Вызов getPreferredCredentials запускает Activity, предлагающее пользователям разрешить сетевой запрос. Если учетные данные сети были сохранены в цифровой связке ключей Thread SDK, они будут возвращены в ваше приложение.

Запрос учетных данных

Чтобы предложить пользователю выбрать предпочтительные учетные данные, выполните следующие действия:

  1. Объявите ActivityLauncher:

    private lateinit var preferredCredentialsLauncher: ActivityResultLauncher<IntentSenderRequest>
    
  2. Обработайте результат действия, возвращенный как ThreadNetworkCredentials:

    preferredCredentialsLauncher =
     registerForActivityResult(
       StartIntentSenderForResult()
     ) { result: ActivityResult ->
       if (result.resultCode == RESULT_OK) {
         val threadNetworkCredentials = ThreadNetworkCredentials.fromIntentSenderResultData(result.data!!)
         Log.d("debug", threadNetworkCredentials.networkName)
       } else {
         Log.d("debug", "User denied request.")
       }
     }
    
  3. Если вы настраиваете новый объект TBR, рекомендуем вызвать preferredCredentials и запустить действие. Этот вызов гарантирует, что новый TBR будет использовать те же учетные данные, которые уже сохранены в телефоне как предпочтительные, что способствует конвергенции различных TBR в одну сеть.

    private fun getPreferredThreadNetworkCredentials() {
      ThreadNetwork.getClient(this)
        .preferredCredentials
      .addOnSuccessListener { intentSenderResult ->
        intentSenderResult.intentSender?.let {
          preferredCredentialsLauncher.launch(IntentSenderRequest.Builder(it).build())
          } ?: Log.d("debug", "No preferred credentials found.")
        }
      .addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
    }
    
  4. Если вы хотите настроить устройства, не являющиеся пограничными маршрутизаторами Thread, например новое конечное устройство Matter-over-Thread, рекомендуем использовать API allActiveCredentials для получения учетных данных. Этот вызов выполнит поиск TBR в локальной сети и не вернет учетные данные, которые недоступны через существующий локальный TBR.

    // Creates the IntentSender result launcher for the getAllActiveCredentials API
    private val getAllActiveCredentialsLauncher =
      registerForActivityResult(
        StartIntentSenderForResult()
      ) { result: ActivityResult ->
        if (result.resultCode == RESULT_OK) {
          val activeCredentials: List<ThreadNetworkCredentials> =
            ThreadNetworkCredentials.parseListFromIntentSenderResultData(
              result.data!!
            )
          // Use the activeCredentials list
        } else {
          // The user denied to share!
        }
      }
    
    // Invokes the getAllActiveCredentials API and starts the dialog activity with the returned
    // IntentSender
    threadNetworkClient
    .getAllActiveCredentials()
    .addOnSuccessListener { intentSenderResult: IntentSenderResult ->
      val intentSender = intentSenderResult.intentSender
      if (intentSender != null) {
        getAllActiveCredentialsLauncher.launch(
          IntentSenderRequest.Builder(intentSender).build()
        )
      } else {
        // No active network credentials found!
      }
    }
    // Handles the failure
    .addOnFailureListener { e: Exception ->
      // Handle the exception
    }
    

Как создать сеть Thread

Если в сети Thread пользователя нет ни предпочтительных, ни активных учетных данных, вы можете добавить их в сервисы Google Play с помощью addCredentials API. Для этого вам нужно создать объект ThreadBorderAgent, а также предоставить объект ThreadNetworkCredentials.

Чтобы создать случайную сеть, вызовите newRandomizeBuilder:

val threadCredentials = ThreadNetworkCredentials.newRandomizedBuilder().build()

Чтобы указать название сети Thread:

val threadCredentials = ThreadNetworkCredentials.newRandomizedBuilder()
  .setNetworkName("ThreadNetworkSDK")
  .build()

Добавление учетных данных

Чтобы учетные данные вашей сети Thread были доступны другим поставщикам Thread, их нужно добавить в сервисы Google Play. Прежде чем добавлять новые учетные данные, нам также нужно знать, к какому устройству TBR относится эта сеть Thread.

В этом примере мы создадим ThreadBorderAgent из идентификатора пограничного агента и передадим новые учетные данные сети Thread, которые вы только что создали:

private fun addCredentials(borderAgentInfo: BorderAgentInfo, credentialsToBeAdded: ThreadNetworkCredentials) {

  val threadBorderAgent = ThreadBorderAgent.newBuilder(borderAgentInfo.borderAgentId).build()
  Log.d("debug", "border router id:" + threadBorderAgent.id)

  ThreadNetwork.getClient(this)
    .addCredentials(threadBorderAgent, credentialsToBeAdded)
      .addOnSuccessListener {
        Log.d("debug", "Credentials added.")
      }
      .addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
}

Обнаружение и перенос border router в полях

Если у вас есть border router, вы можете использовать isPreferredCredentials, чтобы определить, принадлежат ли они border router к предпочтительной сети. Этот API не запрашивает у пользователя разрешение и проверяет учетные данные border router, сравнивая их с теми, что хранятся в сервисах Google Play.

Функция isPreferredCredentials возвращает 0, если совпадение не найдено, и 1, если найдено. Тип данных – Int. Проверить результаты можно с помощью IsPreferredCredentialsResult.

public @interface IsPreferredCredentialsResult {
    int PREFERRED_CREDENTIALS_NOT_FOUND = -1;
    int PREFERRED_CREDENTIALS_NOT_MATCHED = 0;
    int PREFERRED_CREDENTIALS_MATCHED = 1;
}

Чтобы использовать isPreferredCredentials, сначала нужно создать объект ThreadNetworkCredentials. Создать экземпляр ThreadNetworkCredentials можно несколькими способами. Ниже мы расскажем о них подробнее.

Учетные данные сети Thread по рабочему набору данных

В некоторых случаях в вашем приложении TBR уже настроена сеть Thread и вы хотите добавить ее в сервисы Google Play, чтобы поделиться ею с другими поставщиками. Вы можете создать экземпляр ThreadNetworkCredential из необработанного списка TLV активного операционного набора данных Thread:

  1. Преобразуйте набор операционных данных в ByteArray. Пример:

    val activeDataset =
          "0e080000000000010000000300000f35060004001fffe0020833333333...".dsToByteArray()
    
    fun String.dsToByteArray(): ByteArray {
      return chunked(2).map { it.toInt(16).toByte() }.toByteArray()
    }
    
  2. Создайте ThreadNetworkCredentials с помощью fromActiveOperationalDataset. Если все пройдет успешно, вы сможете получить название сети Thread, канал и другую информацию о сети. Полный список свойств можно найти в разделе ThreadNetworkCredentials.

    val threadNetworkCredentials =
        ThreadNetworkCredentials.fromActiveOperationalDataset(activeDataset)
    Log.d(
        "threadNetworkCredentials",
        threadNetworkCredentials.channel.toString() + " - " + threadNetworkCredentials.networkName)
    
  3. Вызовите API isPreferredCredentials и передайте ThreadNetworkCredentials.

    ThreadNetwork.getClient(this)
    .isPreferredCredentials(threadNetworkCredentials)
    .addOnSuccessListener { result ->
      when (result) {
        IsPreferredCredentialsResult.PREFERRED_CREDENTIALS_NOT_MATCHED ->
            Log.d("isPreferredCredentials", "Credentials not matched.")
        IsPreferredCredentialsResult.PREFERRED_CREDENTIALS_MATCHED ->
            Log.d("isPreferredCredentials", "Credentials matched.")
      }
    }
    .addOnFailureListener { e: Exception -> Log.d("isPreferredCredentials", "ERROR: [${e}]") }
    

Учетные данные сети Thread от пограничного агента

Идентификатор пограничного агента уникален для каждого устройства TBR. Чтобы использовать API getCredentialsByBorderAgent, сначала создайте объект ThreadBorderAgent и передайте идентификатор пограничного агента.

После создания объекта ThreadBorderAgent вызовите метод getCredentialsByBorderAgent. Если учетные данные сохранены, проверьте, являются ли они предпочтительными.

private fun isPreferredThreadNetworkByBorderAgent(borderAgentInfo: BorderAgentInfo) {

  val threadBorderAgent = ThreadBorderAgent.newBuilder(borderAgentInfo.borderAgentId).build()
  Log.d("debug", "border router id:" + threadBorderAgent.id)

  var isPreferred = IsPreferredCredentialsResult.PREFERRED_CREDENTIALS_NOT_FOUND
  var borderAgentCredentials: ThreadNetworkCredentials?
  val taskByBorderAgent = ThreadNetwork.getClient(this)
  taskByBorderAgent
      .getCredentialsByBorderAgent(threadBorderAgent)
      .addOnSuccessListener { result: ThreadNetworkCredentialsResult ->
        borderAgentCredentials = result.credentials
        result.credentials?.let {
          taskByBorderAgent.isPreferredCredentials(it).addOnSuccessListener { result ->
            isPreferred = result
          }
        }
      }
      .addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
}

Учетные данные сети Thread по расширенному идентификатору PAN

Как и в случае с getPreferredCredentials, вы можете запросить у пользователя учетные данные из расширенного идентификатора PAN TBR. getCredentialsByExtendedPanId возвращает IntentSender, а результат действия содержит объект ThreadNetworkCredentials, когда пользователь дает разрешение.

private fun getCredentialsByExtPanId(borderAgentInfo: BorderAgentInfo) {
  ThreadNetwork.getClient(this)
    .getCredentialsByExtendedPanId(borderAgentInfo.extPanId)
    .addOnSuccessListener { intentSenderResult ->
      intentSenderResult.intentSender?.let {
        preferredCredentialsLauncher.launch(IntentSenderRequest.Builder(it).build())
      }
        ?: Log.d("debug", "No credentials found.")
    }
    .addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
}

Удалить учетные данные

Если вы удалили устройство border router из дома или сбросили его настройки, вам нужно удалить его сеть Thread из сервисов Google Play.

private fun removeCredentials(borderAgentInfo: BorderAgentInfo) {

  val threadBorderAgent = ThreadBorderAgent.newBuilder(borderAgentInfo.borderAgentId).build()
  Log.d("debug", "border router id:" + threadBorderAgent.id)

  ThreadNetwork.getClient(this)
      .removeCredentials(threadBorderAgent)
      .addOnSuccessListener { Log.d("debug", "Credentials removed.") }
      .addOnFailureListener { e: Exception -> Log.d(TAG, "ERROR: [${e}]") }
}

Ресурсы

Подробную информацию о Thread Network SDK можно найти в документации по API.