Thread-Netzwerk mit Thread-Anmeldedaten von Google teilen

1. Hinweis

In unserem Codelab Thread Border Router (TBR) zeigen wir, wie Sie einen Thread Border Router auf Basis eines Raspberry Pi erstellen. In diesem Codelab haben wir

  • Bidirektionale IP-Verbindung zwischen Thread- und WLAN-/Ethernet-Netzwerken herstellen.
  • Bidirektionale Diensterkennung über mDNS (über WLAN/Ethernet-Link) und SRP (über Thread-Netzwerk) bereitstellen.

Dieses Codelab baut auf dem vorherigen auf und befasst sich damit, wie Ihr eigener Border-Router und Ihre App mit Google APIs interagieren können, um ein einzelnes Thread-Netzwerk zu bilden. Die Zusammenführung von Thread-Anmeldedaten ist wichtig, da sie die Netzwerkrobustheit erhöht und die Nutzerinteraktionen mit den Anwendungen vereinfacht, die auf Thread basieren.

Vorbereitung

  • OTBR Codelab absolvieren
  • Grundkenntnisse in Linux, Android/Kotlin und Thread-Netzwerken

Lerninhalte

  • Anmeldedatensätze mit den Thread-Freigabe-APIs abrufen und festlegen
  • Eigenen OpenThread-Border-Router mit denselben Anmeldedaten wie das Google-Netzwerk einrichten

Voraussetzungen

  • Raspberry Pi 4 oder ein anderes Linux-basiertes Board mit dem Open Thread Border Router (OTBR)
  • Platine, die IEEE 802.15.4-Konnektivität als Radio Co-Processor (RCP) bietet. Eine Liste der Repositories verschiedener SoC-Anbieter und deren Anleitungen finden Sie auf der OpenThread-GitHub-Seite.

2. HTTP-Dienst einrichten

Der erste Baustein, den wir benötigen, ist eine Schnittstelle, mit der wir aktive Anmeldedaten lesen und ausstehende Anmeldedaten in Ihre OTBR schreiben können. Verwenden Sie beim Erstellen einer TBR Ihre eigenen proprietären Mechanismen, wie hier anhand von zwei Beispielen gezeigt. Die erste Option zeigt, wie Sie lokal über DBUS mit dem OTBR-Agenten kommunizieren. Bei der zweiten Option wird die Rest API verwendet, die auf dem OTBR aufgebaut werden kann.

Keine der beiden Methoden ist sicher und sollte nicht unverändert in einer Produktionsumgebung verwendet werden. Ein Anbieter kann jedoch eine Verschlüsselung für eine der beiden Methoden implementieren, um sie in einer Produktionsumgebung zu verwenden. Sie können auch Ihren eigenen Monitordienst erweitern, um loopback-HTTP- oder lokal ausgeführte DBUS-Aufrufe auszuführen.

Option 1: DBUS- und HTTP-API in Python-Script

91e5fdeed83e9354.png

In diesem Schritt wird ein einfacher HTTP-Dienst erstellt, der zwei Endpunkte zum Lesen und Festlegen von Anmeldedaten bereitstellt und letztendlich DBUS-Befehle aufruft.

Installieren Sie auf dem RPi, das als OTBR dient, die Python 3-Abhängigkeiten:

$ pip install dbus-python shlex json

Führen Sie das Script als

$  sudo python credentials_server.py 8081
serving at port 8081

Im Beispiel wird ein HTTP-Server an Port 8081 eingerichtet und wartet am Stammpfad entweder auf eine GET-Anfrage zum Abrufen von Thread-Anmeldedaten oder auf eine POST-Anfrage zum Festlegen von Thread-Anmeldedaten. Die Nutzlast ist immer eine JSON-Struktur mit dem TLV.

Mit der folgenden PUT-Anfrage werden dem OTBR über den Pfad /node/dataset/pending neue Anmeldedaten für ausstehende Threads zugewiesen. In diesem Fall werden die ausstehenden Anmeldedaten innerhalb von 10 Sekunden angewendet:

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
}

Mit einer GET-Anfrage an /node/dataset/active werden die derzeit aktiven Anmeldedaten abgerufen.

GET /node/dataset/active
Host: <IP>:8081
ContentType = "application/json"
acceptMimeType = "text/plain"
...
<TLV encoded Thread Dataset>

Das Script ruft DBUS-Lese-/Schreibbefehle für den Buspfad io.openthread.BorderRouter.wpan0 und den Objektpfad /io/openthread/BorderRouter/wpan0 auf:

# 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 ermöglicht die Inspektion seiner Funktionen. Dazu haben Sie folgende Möglichkeiten:

$ sudo dbus-send --system --dest=io.openthread.BorderRouter.wpan0 \
        --type=method_call --print-reply /io/openthread/BorderRouter/wpan0 \
        org.freedesktop.DBus.Introspectable.Introspect

Eine Liste der unterstützten Funktionen finden Sie hier.

Option 2: Native HTTP-REST API des OTBR-Agents

c748ca5151b6cacb.png

Der OpenThread-Border-Router wird standardmäßig mit dem Flag REST_API=1 erstellt, wodurch die REST API aktiviert wird. Falls die REST API in Ihrem Build aus einem vorherigen Codelab nicht aktiviert wurde, müssen Sie OTBR auf Ihrem RPi mit diesem Flag erstellen:

$ REST_API=1 INFRA_IF_NAME=wlan0 ./script/setup

Ein OTBR-Agent kann mit dem folgenden Befehl neu gestartet werden:

$ sudo systemctl restart otbr-agent.service

Der Agent startet einen HTTP-Server auf Port 8081. Über diesen Server können Nutzer oder Monitorprogramme viele Aufgaben im OTBR ausführen (hier dokumentiert). Sie können den Inhalt mit Ihrem Browser, curl oder wget prüfen. Zu den vielen unterstützten Pfaden gehören die oben beschriebenen Anwendungsfälle mit dem Verb GET auf /node/dataset/active und dem Verb PUT auf /node/dataset/pending.

3. Anmeldedaten-Framework auf Android-Geräten einrichten

Bevorzugte Anmeldedaten

Die Google Play-Dienste auf Android ermöglichen und erfordern die Registrierung von Anmeldedaten für alle TBRs in Ihrem Netzwerk. Jeder wird durch seine Border Router Agent ID (BAID) identifiziert. Sie verwenden dazu die Methode addCredentials() der Schnittstelle ThreadNetworkClient. Die bevorzugten Anmeldedaten für dieses Mobilgerät werden anhand des ersten TBR bestimmt, der dem Speicher der Google Play-Dienste hinzugefügt wird.

Die App, die ihrer BAID Anmeldedaten für das Thread-Netzwerk hinzufügt, wird zum Inhaber der Anmeldedaten und hat volle Berechtigungen für den Zugriff darauf. Wenn Sie versuchen, auf Anmeldedaten zuzugreifen, die von anderen Apps hinzugefügt wurden, erhalten Sie den Fehler PERMISSION_DENIED. Die bevorzugten Anmeldedaten sind jedoch mit der Einwilligung des Nutzers immer für jede App verfügbar. Wir empfehlen, die in den Google Play-Diensten gespeicherten Anmeldedaten auf dem neuesten Stand zu halten, wenn das Thread Border Router-Netzwerk aktualisiert wird. Diese Informationen werden derzeit nicht verwendet, aber wir werden in Zukunft möglicherweise erweiterte Suchanfragen anbieten.

Auch wenn die erste TBR später ausgeschlossen wird, bleiben die bevorzugten Anmeldedaten auf dem Android-Gerät erhalten. Nach der Einrichtung können andere Apps, die Thread-Anmeldedaten verwalten, die Anmeldedaten über einen getPreferredCredentials()-Aufruf abrufen.

Google TBR Sync

Android-Geräte werden automatisch mit Google-TBRs synchronisiert. Wenn auf Android-Geräten keine Anmeldedaten vorhanden sind, werden sie aus den Google-TBRs in Ihrem Netzwerk extrahiert und diese Anmeldedaten werden zu den bevorzugten Anmeldedaten. Die Synchronisierung zwischen TBRs und dem Android-Gerät erfolgt nur, wenn der TBR mit einem einzelnen Nutzer oder mit zwei Nutzern gekoppelt ist, die sich im selben Smart Home (Gebäude) befinden.

Dies geschieht auch, wenn sich ein anderer Google-Nutzer in GHA für Android oder GHA für iOS befindet und sich im selben Gebäude befindet. Bei GHA für iOS werden die bevorzugten Anmeldedaten im iOS-Speicher festgelegt, wenn keine bevorzugten Anmeldedaten vorhanden sind.

Wenn sich zwei Android-Geräte (oder Android und iGHA) im selben Netzwerk mit unterschiedlichen bevorzugten Anmeldedaten befinden, hat das Gerät, auf dem die TBR ursprünglich konfiguriert wurde, Vorrang.

Onboarding von Drittanbietern für TBR

Der Speicherort der Anmeldedaten ist derzeit nicht auf das Smart Home (Gebäude) des Nutzers beschränkt. Jedes Android-Gerät hat seinen eigenen BAID-Speicher. Sobald sich jedoch ein Google TBR im Netzwerk befindet, synchronisieren sich andere Android-Geräte und iOS-Geräte mit der Google Home App für iOS mit diesem TBR und versuchen, lokale Anmeldedaten im Smartphone-Speicher festzulegen.

Bevor ein neues OOB-TBR ein Netzwerk erstellt, ist es wichtig zu prüfen, ob im Android-Speicher bereits ein bevorzugtes Netzwerk vorhanden ist.

  • Wenn ein bevorzugtes Netzwerk vorhanden ist, sollte der Anbieter es verwenden. So wird sichergestellt, dass Thread-Geräte nach Möglichkeit mit einem einzigen Thread-Netzwerk verbunden sind.
  • Wenn kein bevorzugtes Netzwerk vorhanden ist, erstellen Sie einen neuen Anmeldedatensatz und weisen Sie ihn Ihrem TBR in den Google Play-Diensten zu. Android verwendet diese Anmeldedaten als Standardanmeldedaten für alle Google-basierten TBRs. Andere Anbieter können die Mesh-Reichweite und -Robustheit mit zusätzlichen Geräten verbessern.

cd8bc726f67b1fa1.png

4. Android-App klonen und ändern

Wir haben eine Android-App erstellt, in der die wichtigsten Aufrufe der Thread API veranschaulicht werden. Sie können diese Muster in Ihrer App verwenden. In diesem Codelab klonen wir die Google Home-Beispiel-App für Matter aus GitHub.

Der gesamte hier gezeigte Quellcode ist bereits in der Beispiel-App codiert. Sie können ihn nach Ihren eigenen Bedürfnissen anpassen. Sie können die App aber auch einfach klonen oder die vorgefertigten Binärdateien ausführen, um die Funktionalität zu prüfen.

  1. Sie können sie mit folgenden Methoden klonen:
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
  1. Laden Sie Android Studio herunter und öffnen Sie es.
  2. Klicken Sie auf „Datei“ > „Öffnen“ und wählen Sie Ihr geklontes Repository aus.
  3. Aktivieren Sie den Entwicklermodus auf Ihrem Android-Smartphone.
  4. Schließen Sie es über ein USB-Kabel an Ihren Computer an.
  5. App über Android Studio mit <Cmd+R> (OS X) oder <Ctrl+R> (Win, Linux) ausführen
  6. Klicken Sie auf das Zahnrad -> „Entwicklertools“ -> „Thread-Netzwerk“.
  7. Interagieren Sie mit den verschiedenen verfügbaren Optionen. In den folgenden Abschnitten sehen wir uns den Code an, der auf jeder Schaltfläche ausgeführt wird.

Gibt es bevorzugte Anmeldedaten?

Die erste Frage, die ein Hersteller von TBR-Geräten an Google stellen sollte, ist, ob bereits eine bevorzugte Anmeldedatensammlung auf dem Gerät vorhanden ist. Dies sollte der Ausgangspunkt Ihres Navigationsflusses sein. Im folgenden Code wird GPS nach der Existenz von Anmeldedaten gefragt. Es wird keine Nutzereinwilligung angefordert, da keine Anmeldedaten freigegeben werden.

/**
* 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)
 }
}

Bevorzugte GPS-Anmeldedaten abrufen

Falls vorhanden, müssen Sie die Anmeldedaten lesen. Der einzige Unterschied zum vorherigen Code besteht darin, dass Sie nach Erhalt der intentSenderResult einen Intent erstellen und mit diesem Ergebnis des Absenders starten möchten.

In unserem Code verwenden wir aus organisatorischen und architektonischen Gründen eine MutableLiveData<IntentSender?>, da sich der ursprüngliche Code im ViewModel (ThreadViewModel.kt) und die Intent-Beobachter im Activity-Fragment (ThreadFragment.kt) befinden. Sobald die intentSenderResult in den Live-Daten gepostet wurde, wird der Inhalt dieses Beobachters ausgeführt:

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()
 }
}

Dadurch wird die Nutzereinwilligung für die Freigabe von Anmeldedaten ausgelöst. Wenn die Einwilligung erteilt wird, werden Inhalte über eine der folgenden Methoden zurückgegeben:

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, "")
   }
 }

Das Posten der Anmeldedaten in MutableLiveData<ThreadNetworkCredentials?> wird unten beschrieben.

GPS-Anmeldedaten festlegen

Unabhängig davon, ob sie vorhanden sind oder nicht, sollten Sie Ihre TBR in den Google Play-Diensten registrieren. Ihre App ist die einzige, die die Anmeldedaten lesen kann, die mit der Border Agent-ID Ihres TBR verknüpft sind. Wenn Ihr TBR jedoch der erste ist, der sich registriert, werden diese Anmeldedaten in den Satz der bevorzugten Anmeldedaten kopiert. Auf diese Informationen kann jede App auf dem Smartphone zugreifen, sofern der Nutzer dies autorisiert.

/**
* 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)
     }
 }
}

Anmeldedaten für dein TBR-Produkt festlegen

Dieser Teil ist proprietär für jeden Anbieter. In diesem Codelab implementieren wir ihn entweder mit dem DBUS+Python HTTP Rest Server oder dem nativen HTTP Rest Server von 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()
)

Anmeldedaten aus Ihrem TBR-Produkt abrufen

Wie bereits erwähnt, verwenden Sie das HTTP-Verb „GET“, um die Anmeldedaten aus Ihrem TBR abzurufen. Python-Beispielskript

Build und Importe

Wenn Sie Ihre Android-App erstellen, müssen Sie Änderungen an Ihrem Manifest, Build und Ihren Importen vornehmen, um das Thread-Modul von Google Play-Diensten zu unterstützen. In den folgenden drei Snippets sind die meisten Ergänzungen zusammengefasst.

Hinweis: Unsere Beispiel-App wurde hauptsächlich für die Matter-Inbetriebnahme entwickelt. Daher sind die Manifest- und Gradle-Dateien komplexer als die Ergänzungen, die für die Verwendung von Thread-Anmeldedaten erforderlich sind.

Manifeständerungen

<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'

Relevante Importe

// 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-Erkennung

Unsere Beispiel-App verwendet die mDNS/SD-Erkennung, um eine Liste der verfügbaren Thread-Border-Router im Netzwerk sowie deren jeweilige BAIDs zu erstellen.

Das ist sehr hilfreich, wenn Sie die Informationen Ihres TBR in den Speicher der GPS-Anmeldedaten eingeben. Die Verwendung von Datasets wird in diesem Codelab jedoch nicht behandelt. Wir verwenden die Android-Diensterkennungsbibliothek NSDManager. Der vollständige Quellcode ist in der Beispiel-App unter ServiceDiscovery.kt verfügbar.

6. Zusammenfassung

Sobald Sie diese Aufrufe implementiert oder die Beispiel-App verwendet haben, können Sie Ihren RPi OTBR vollständig einrichten. Unsere Beispiel-App enthält acht Schaltflächen:

91979bf065e9673d.png

Eine mögliche Abfolge für das Onboarding Ihres TBR:

  1. Abfragen, ob bevorzugte Anmeldedaten vorhanden sind (blau, 1. Zeile)
  2. Je nach Antwort
  3. Bevorzugte GPS-Anmeldedaten abrufen (blau, 2. Zeile)
  4. TBR-Anmeldedaten in GPS festlegen (blau, 3. Zeile) -> TBR auswählen -> Zufällig erstellen -> Netzwerknamen eingeben -> Ok
  5. Nachdem Sie die bevorzugten Anmeldedaten festgelegt haben, können Sie sie mit RPi OTBR-Anmeldedaten festlegen für Ihren OTBR festlegen. Diese Anmeldedaten werden dann auf den ausstehenden Satz angewendet.

In der Beispiel-App wird standardmäßig eine Verzögerung von 10 Sekunden verwendet. Nach diesem Zeitraum werden die Anmeldedaten Ihres RPi TBR (und anderer Knoten, die sich in seinem Netzwerk befinden) in den neuen Datensatz migriert.

7. Fazit

In diesem Codelab haben wir eine Beispiel-Android-App geklont und mehrere Code-Snippets analysiert, die die Thread Storage APIs der Google Play-Dienste nutzen. Mit diesen APIs haben wir einen gemeinsamen Datensatz erstellt, den wir in einem RPi-TBR einbinden können, der die TBR eines Anbieters zeigt.

Wenn sich alle TBR eines Nutzers im selben Netzwerk befinden, wird die Resilienz und Reichweite des Thread-Netzwerks verbessert. Außerdem wird verhindert, dass Nutzer aufgrund fehlender Anmeldedaten nicht auf Thread-Geräte zugreifen können.

Wir hoffen, dass Ihnen dieses Codelab und die Beispiel-Apps dabei helfen, Ihre eigene App und Ihr Thread-Border-Router-Produkt zu entwerfen und zu entwickeln.

8. Verweise

RCP-Coprozessor

DBUS