1. Antes de começar
No codelab Roteador de borda do Thread (TBR, na sigla em inglês), mostramos como criar um roteador de borda do Thread com base em um Raspberry Pi. Nesse codelab,
- Estabeleça a conectividade IP bidirecional entre redes Thread e Wi-Fi/Ethernet.
- Ofereça descoberta de serviço bidirecional por mDNS (no link Wi-Fi/Ethernet) e SRP (na rede Thread).
Este codelab baseia-se no anterior e aborda como seu roteador de borda e seu app podem interagir com as APIs do Google para criar uma única rede Thread. Convergar credenciais do Thread é importante porque aumenta a robustez da rede e simplifica as interações do usuário com os aplicativos que dependem do Thread.
Pré-requisitos
- Concluir o Codelab do OTBR
- Conhecimento básico de Linux, Android/Kotlin e rede Thread
O que você vai aprender
- Como usar as APIs de compartilhamento de thread para receber e definir conjuntos de credenciais.
- Como configurar seu próprio roteador de borda do OpenThread com as mesmas credenciais da rede do Google
O que é necessário
- Placa Raspberry Pi 4 ou outra placa baseada em Linux que executa o roteador de borda Open Thread (OTBR)
- Placa que fornece conectividade IEEE 802.15.4 como um processador de rádio (RCP). Veja uma lista de repositórios de diferentes fornecedores de SoC e as instruções deles na página do GitHub sobre o OpenThread (em inglês).
2. Como configurar o serviço HTTP
O primeiro elemento básico de que precisamos é uma interface que nos permita ler as Credenciais ativas e gravar as Credenciais pendentes no seu OTBR. Ao criar uma TBR, use seus mecanismos reservados, conforme mostrado aqui com dois exemplos. A primeira opção mostra como interagir localmente com o agente OTBR via DBUS, enquanto a segunda aproveita a API REST, que pode ser criada no OTBR.
Nenhum dos métodos é seguro e não deve ser usado no estado em que se encontra em um ambiente de produção. No entanto, um fornecedor pode criar criptografia em torno de qualquer um dos métodos para usá-lo em um ambiente de produção, ou você pode estender seu próprio serviço de monitoramento para emitir chamadas HTTP de loopback ou chamadas DBUS locais inerentemente.
Opção 1: DBUS e API HTTP no script Python
Esta etapa cria um serviço HTTP básico que expõe dois endpoints para ler e definir credenciais e, por fim, chamar comandos DBUS.
Na RPi que servirá como seu OTBR, instale as dependências do Python 3:
$ pip install dbus-python shlex json
Execute o script como:
$ sudo python credentials_server.py 8081 serving at port 8081
O exemplo configura um servidor HTTP na porta 8081 e detecta no caminho raiz uma solicitação GET para recuperar credenciais do Thread ou uma solicitação POST para definir credenciais do Thread. O payload é sempre uma estrutura JSON com o TLV.
A solicitação PUT a seguir define novas credenciais de linha de execução pendentes para o OTBR por meio do caminho /node/dataset/pending
. Nesse caso, as credenciais pendentes são aplicadas em 10 segundos:
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 }
Uma solicitação GET para /node/dataset/active
busca as credenciais ativas no momento.
GET /node/dataset/active Host: <IP>:8081 ContentType = "application/json" acceptMimeType = "text/plain" ... <TLV encoded Thread Dataset>
O script chama comandos R/W do DBUS para o caminho do barramento io.openthread.BorderRouter.wpan0
, o caminho do objeto /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)
O DBUS permite introspecção dos próprios recursos. Você pode fazer isso da seguinte maneira:
$ sudo dbus-send --system --dest=io.openthread.BorderRouter.wpan0 \ --type=method_call --print-reply /io/openthread/BorderRouter/wpan0 \ org.freedesktop.DBus.Introspectable.Introspect
Você também pode conferir os recursos compatíveis documentados aqui.
Opção 2: API HTTP REST nativa do agente OTBR
O roteador de borda do OpenThread é criado por padrão com a flag REST_API=1
, ativando a API REST. Caso o build de um codelab anterior não tenha ativado a API REST, crie o OTBR na sua RPi com essa flag:
$ REST_API=1 INFRA_IF_NAME=wlan0 ./script/setup
Um agente OTBR pode ser reiniciado executando:
$ sudo systemctl restart otbr-agent.service
O agente inicia um servidor HTTP na porta 8081. Esse servidor permite que um usuário ou programa de monitoramento execute muitas tarefas no OTBR (documentado aqui). Use o navegador, curl
ou wget
, para inspecionar o conteúdo. Entre os muitos caminhos aceitos estão os casos de uso descritos acima, com o verbo GET
em /node/dataset/active
e o verbo PUT
em /node/dataset/pending
3. Configurar o Credential Framework no Android
Credenciais preferenciais
O Google Play Services no Android permite e espera o registro de credenciais para todos os TBRs na sua rede. Cada um é identificado pelo seu ID do agente de roteador de borda (BAID, na sigla em inglês). Você vai usar o método addCredentials()
da interface ThreadNetworkClient
para realizar essa tarefa. O primeiro TBR adicionado ao armazenamento do Google Play Services determina as credenciais preferenciais para esse dispositivo móvel.
O app que adiciona um conjunto de credenciais da rede Thread à BAID se torna o proprietário das credenciais e tem permissões totais para acessá-las. Se você tentar acessar as credenciais adicionadas por outros apps, vai receber um erro PERMISSION_DENIED. No entanto, as credenciais preferenciais estão sempre disponíveis para qualquer app mediante consentimento do usuário. Recomendamos que você mantenha as credenciais armazenadas no Google Play Services atualizadas quando a rede do roteador de borda do Thread for atualizada. Embora essas informações não sejam usadas atualmente, poderemos oferecer jornadas aprimoradas no futuro.
Mesmo que o primeiro TBR seja excluído posteriormente, as credenciais preferenciais continuarão no dispositivo Android. Depois disso, outros apps que gerenciam as credenciais do Thread podem receber as credenciais de uma chamada getPreferredCredentials()
.
Google TBR Sync (em inglês)
Os dispositivos Android são sincronizados automaticamente com as TBRs do Google. Se não houver credenciais no Android, os dispositivos as extraem das TBRs do Google na sua rede, e essas credenciais se tornam as credenciais preferenciais. A sincronização entre TBRs e o dispositivo Android acontece somente se a TBR estiver pareada com um único usuário ou com dois usuários que estão na mesma casa inteligente ( Estrutura).
Esse processo também vai acontecer quando outro usuário do Google estiver no GHA para Android ou no GHA para iOS quando o usuário estiver na mesma estrutura. No caso do GHA para iOS, as credenciais preferenciais são definidas no armazenamento do iOS, se não houver nenhuma.
Se dois dispositivos Android (ou Android + iGHA) estiverem na mesma rede com conjuntos diferentes de credenciais preferenciais, o dispositivo que configurou originalmente a TBR prevalecerá na TBR.
Integração da TBR de terceiros
No momento, o armazenamento da credencial não está no escopo da casa inteligente do usuário ( Estrutura). Cada dispositivo Android terá o próprio armazenamento BAID, mas assim que houver uma TBR do Google na rede, outros dispositivos Android e iOS que executam o app Google Home para iOS serão sincronizados com essa TBR e tentarão definir credenciais locais no armazenamento do smartphone.
Antes que um novo TBR OOB crie uma rede, é importante verificar se uma rede preferencial já existe no armazenamento do Android.
- Se existir uma rede preferencial, o fornecedor deverá usá-la. Isso garante que os dispositivos Thread estejam conectados a uma única rede Thread quando possível.
- Quando não houver uma rede preferencial, crie um novo conjunto de credenciais e atribua-o ao TBR no Google Play Services. O Android vai usar essas credenciais como padrão definidas em todas as TBRs baseadas no Google, e outros fornecedores poderão aumentar o alcance e a robustez da sua malha com outros dispositivos.
4. Como clonar e modificar o app Android
Criamos um app Android que mostra as principais chamadas possíveis para a API Thread. Você pode usar esses padrões no seu app. Neste codelab, vamos clonar o app de exemplo do Google Home para o Matter do GitHub.
Todo o código-fonte mostrado aqui já está codificado no app de exemplo. Você pode fazer modificações de acordo com suas necessidades, mas é possível simplesmente clonar o app ou executar os binários pré-criados para inspecionar a funcionalidade.
- Clone usando:
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
- Faça o download e abra o Android Studio.
- Clique em File > Open (Arquivo > Abrir) e selecione seu repositório clonado.
- Ative o modo de desenvolvedor no smartphone Android.
- Conecte-o ao computador com um cabo USB.
- Execute o app no Android Studio usando <Cmd+R> (OS X) ou <Ctrl+R> (Win, Linux)
- Navegue até a Roda -> Utilitários do desenvolvedor -> Rede Thread
- Interaja com as diferentes opções disponíveis. Nas seções abaixo, vamos descompactar o código que é executado em cada botão.
Existem credenciais preferenciais?
A primeira pergunta que um fabricante de TBR deve fazer ao Google é se já existe um conjunto preferencial de credenciais no dispositivo. Esse deve ser o ponto de partida do seu fluxo. O código abaixo consulta o GPS sobre a existência das credenciais. Ele não solicita o consentimento do usuário porque nenhuma credencial é compartilhada.
/** * 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) } }
Receber credenciais preferenciais de GPS
Se elas existirem, você vai precisar ler as credenciais. A única diferença do código anterior é que, depois de receber o intentSenderResult
, você quer criar e iniciar uma intent usando esse resultado do remetente.
No nosso código, para fins de organização/arquitetura, usamos um MutableLiveData<IntentSender?>
porque o código original está no ViewModel (ThreadViewModel.kt) e os observadores de intent estão no fragmento de atividade ( ThreadFragment.kt). Assim, depois que o intentSenderResult for postado nos dados ativos, vamos executar o conteúdo desse observador:
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() } }
Isso vai acionar o consentimento do usuário com credenciais de compartilhamento e, se aprovado, vai retornar o conteúdo usando:
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, "") } }
Veja a seguir uma descrição para postar as credenciais em MutableLiveData<ThreadNetworkCredentials?>
.
Configurar credenciais de GPS
Independentemente de existir ou não, você deve registrar seu TBR no Google Play Services. Seu app será o único capaz de ler as credenciais associadas ao ID do agente de borda do seu TBR. No entanto, se o TBR for o primeiro a ser registrado, essas credenciais serão copiadas para o conjunto de credenciais preferenciais. Essas informações podem ser acessadas por qualquer aplicativo no telefone, desde que o usuário a autorize.
/** * 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) } } }
Como configurar as credenciais para o produto TBR
Essa parte é reservada para cada fornecedor e, neste codelab, ela será implementada usando o servidor HTTP REST DBUS+Python ou o servidor REST nativo do 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() )
Como conseguir as credenciais do produto TBR
Como mostrado anteriormente, use o verbo HTTP GET para receber as credenciais da sua TBR. Consulte o exemplo de script do Python.
Criação e importação
Ao criar seu app Android, você vai precisar fazer mudanças no manifesto, build e importações para oferecer suporte ao módulo Thread do Google Play Services. Os três snippets a seguir resumem a maioria das adições.
Nosso app de exemplo foi criado principalmente para o comissionamento do Matter. Portanto, os arquivos Manifest e Gradle são mais complexos do que as adições necessárias para usar apenas credenciais de linha de execução.
Mudanças no manifesto
<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'
Importações relevantes
// 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. Descoberta de mDNS/SD
Nosso app de exemplo usa a descoberta de mDNS/SD para criar uma lista de roteadores de borda do Thread disponíveis na rede, assim como os respectivos BAIDs.
Isso é muito útil ao inserir as informações do seu TBR no armazenamento da credencial de GPS. No entanto, o uso deles está além do escopo deste codelab. Usamos a biblioteca Android Service Discovery NSDManager, e o código-fonte completo está disponível no app de exemplo em ServiceDiscovery.kt
.
6. Reunindo tudo
Depois de implementar essas chamadas ou usar o app de exemplo, será possível integrar totalmente o OTBR de RPi. Nosso app de exemplo expõe oito botões:
Uma sequência possível para integrar sua TBR é:
- Consultar se há credenciais preferenciais (azul, primeira linha)
- Dependendo da resposta
- Solicitar credenciais preferenciais de GPS (azul, 2a linha)
- Definir credenciais TBR no GPS (azul, 3a linha) -> Selecionar a TBR -> Criar aleatória -> Inserir o nome da rede -> Ok
- Agora que você tem credenciais preferenciais, defina-as como seu OTBR usando Definir credenciais RPi OTBR, que vão aplicar essas credenciais ao conjunto pendente.
O padrão para o app de exemplo é usar um atraso de 10 segundos. Assim, após esse período, as credenciais do seu TBR da RPi (e outros nós que possam existir na rede) serão migradas para o novo conjunto de dados.
7. Conclusão
Neste codelab, clonamos um app Android de exemplo e analisamos vários snippets de código que usam as APIs Thread Storage do Google Play Services. Usamos essas APIs para ter um conjunto de dados comum que podemos integrar em um TBR da RPi, que mostra a TBR de um fornecedor.
ter todo o TBR de um usuário na mesma rede melhora a resiliência e o alcance da rede Thread. Isso também evita jornadas falhas do usuário em que os apps não podem integrar dispositivos Thread porque não têm acesso a credenciais.
Esperamos que este codelab e os apps de exemplo ajudem você a projetar e desenvolver seu próprio app e o roteador de borda Thread.
8. Referências
Coprocessador de RCP
DBUS