Geräte unter Android steuern

Prüfen, ob ein Merkmal einen Befehl unterstützt

Der Support kann auch nach einem Befehl für Merkmale suchen. Mit der Funktion supports auf Merkmalebene können Sie auch prüfen, ob ein Befehl für ein bestimmtes Gerät unterstützt wird.

So prüfen Sie beispielsweise, ob ein Gerät den Befehl toggle für die Eigenschaft „An/Aus“ unterstützt:

// Check if the OnOff trait supports the toggle command.
if (onOffTrait.supports(OnOff.Command.Toggle)) {
  println("onOffTrait supports toggle command")
} else {
  println("onOffTrait does not support stateful toggle command")
}

Befehl an Geräte senden

Das Senden eines Befehls ähnelt dem Lesen eines Statusattributs aus einem Attribut. Wenn Sie das Gerät ein- oder ausschalten möchten, verwenden Sie den Befehl „An/Aus“ des Attributs OnOff, das im Datenmodell des Google Home-Systems als toggle() definiert ist. Mit dieser Methode wird onOff in false geändert, wenn es true ist, oder in true, wenn es false ist:

// Calling a command on a trait.
try {
  onOffTrait.toggle()
} catch (e: HomeException) {
  // Code for handling the exception
}

Alle Attributbefehle sind suspend-Funktionen und werden erst abgeschlossen, wenn eine Antwort von der API zurückgegeben wird, z. B. eine Bestätigung, dass sich der Gerätestatus geändert hat. Befehle können eine Ausnahme zurückgeben, wenn ein Problem mit dem Ausführungsablauf erkannt wird. Als Entwickler sollten Sie einen try-catch-Block verwenden, um diese Ausnahmen ordnungsgemäß zu behandeln und Nutzern detaillierte Informationen zu Fällen zur Verfügung zu stellen, in denen die Fehler behoben werden können. Unbehandelte Ausnahmen beenden die App-Laufzeit und können zu Abstürzen Ihrer App führen.

Alternativ können Sie den Status mit den Befehlen off() oder on() explizit festlegen:

onOffTrait.off()
onOffTrait.on()

Nachdem Sie einen Befehl zum Ändern des Status gesendet haben, können Sie ihn wie unter Gerätestatus lesen beschrieben lesen, um ihn in Ihrer App zu verarbeiten. Alternativ können Sie auch Abläufe verwenden, wie unter Status beobachten beschrieben. Dies ist die bevorzugte Methode.

Befehl mit Parametern senden

Für einige Befehle können Parameter verwendet werden, z. B. die für die Eigenschaften OnOff oder LevelControl:

offWithEffect

// Turn off the light using the DyingLight effect.
onOffTrait.offWithEffect(
  effectIdentifier = OnOffTrait.EffectIdentifierEnum.DyingLight,
  effectVariant = 0u,
)

moveToLevel

// Change the brightness of the light to 50%
levelControlTrait.moveToLevel(
  level = 127u.toUByte(),
  transitionTime = null,
  optionsMask = LevelControlTrait.OptionsBitmap(),
  optionsOverride = LevelControlTrait.OptionsBitmap(),
)

Einige Befehle haben optionale Argumente, die nach den erforderlichen Argumenten kommen.

Der Befehl step für das Attribut FanControl hat beispielsweise zwei optionale Argumente:

val fanControlTraitFlow: Flow<FanControl?> =
  device.type(FanDevice).map { it.standardTraits.fanControl }.distinctUntilChanged()

val fanControl = fanControlTraitFlow.firstOrNull()

// Calling a command with optional parameters not set.
fanControl?.step(direction = FanControlTrait.StepDirectionEnum.Increase)

// Calling a command with optional parameters.
fanControl?.step(direction = FanControlTrait.StepDirectionEnum.Increase) { wrap = true }

Prüfen, ob ein Merkmal ein Attribut unterstützt

Einige Geräte unterstützen möglicherweise eine Matter-Eigenschaft, aber nicht ein bestimmtes Attribut. Ein Cloud-to-cloud-Gerät, das Matter zugeordnet wurde, unterstützt beispielsweise möglicherweise nicht alle Matter-Attribute. Verwenden Sie in solchen Fällen die Funktion supports auf Merkmalebene und das Attribute-Enum des Merkmals, um zu prüfen, ob das Attribut für ein bestimmtes Gerät unterstützt wird.

So prüfen Sie beispielsweise, ob ein Gerät das Attribut onOff der Eigenschaft „An/Aus“ unterstützt:

// Check if the OnOff trait supports the onOff attribute.
if (onOffTrait.supports(OnOff.Attribute.onOff)) {
  println("onOffTrait supports onOff state")
} else {
  println("onOffTrait is for a command only device!")
}

Einige Attribute sind in der Matter-Spezifikation oder im Cloud-to-cloud smart home-Schema nullable. Bei diesen Attributen können Sie mithilfe von isNullable zusätzlich zu supports feststellen, ob der vom Attribut zurückgegebene Wert null ist, weil das Gerät diesen Wert nicht meldet, oder ob der Wert des Attributs tatsächlich null ist:

// Check if a nullable attribute is set or is not supported.
if (onOffTrait.supports(OnOff.Attribute.startUpOnOff)) {
  // The device supports startupOnOff, it is safe to expect this value in the trait.
  if (OnOff.Attribute.startUpOnOff.isNullable && onOffTrait.startUpOnOff == null) {
    // This value is nullable and set to null. Check the specification as to
    // what null in this case means
    println("onOffTrait supports startUpOnOff and it is null")
  } else {
    // This value is nullable and set to a value.
    println("onOffTrait supports startUpOnOff and it is set to ${onOffTrait.startUpOnOff}")
  }
} else {
  println("onOffTrait does not support startUpOnOff!")
}

Merkmalattribute aktualisieren

Wenn Sie den Wert eines bestimmten Attributs ändern möchten und keiner der Befehle des Merkmals dies tut, kann es sein, dass der Wert des Attributs explizit festgelegt werden kann.

Ob der Wert eines Attributs geändert werden kann, hängt von zwei Faktoren ab:

  • Ist das Attribut beschreibbar?
  • Kann sich der Wert des Attributs als Nebeneffekt des Sendens eines Attributbefehls ändern?

Diese Informationen finden Sie in der Referenzdokumentation zu Merkmalen und ihren Attributen.

Die Kombinationen von Eigenschaften, die festlegen, wie der Wert eines Attributs geändert werden kann, sind daher:

  • Schreibgeschützt und nicht von anderen Befehlen betroffen. Das bedeutet, dass sich der Wert des Attributs nicht ändert. Beispiel: das Attribut currentPosition des Merkmals Switch

  • Schreibgeschützt und von anderen Befehlen betroffen. Das bedeutet, dass sich der Wert des Attributs nur durch das Senden eines Befehls ändern kann. Das Attribut currentLevel der Eigenschaft LevelControl Matter ist beispielsweise nur lesbar, sein Wert kann aber mit Befehlen wie moveToLevel geändert werden.

  • Schreibbar und nicht von anderen Befehlen betroffen. Das bedeutet, dass Sie den Wert des Attributs direkt mithilfe der update-Funktion des Merkmals ändern können. Es gibt jedoch keine Befehle, die sich auf den Wert des Attributs auswirken. Beispiel: das Attribut WrongCodeEntryLimit des Merkmals DoorLock

  • Schreibbar und von anderen Befehlen betroffen. Das bedeutet, dass Sie den Wert des Attributs direkt mithilfe der update-Funktion des Attributs ändern können. Der Wert des Attributs kann sich auch durch das Senden eines Befehls ändern. So kann beispielsweise das occupiedCoolingSetpoint-Attribut des Thermostat-Attributs geschrieben, aber auch mit dem Befehl setpointRaiseLower aktualisiert werden.

Beispiel für die Verwendung der Update-Funktion zum Ändern des Werts eines Attributs

In diesem Beispiel wird gezeigt, wie Sie den Wert des Attributs DoorLockTrait.WrongCodeEntryLimit explizit festlegen.

Wenn Sie einen Attributwert festlegen möchten, rufen Sie die update-Funktion des Merkmals auf und übergeben Sie ihr eine Mutatorfunktion, die den neuen Wert festlegt. Es empfiehlt sich, zuerst zu prüfen, ob das Attribut vom Merkmal unterstützt wird.

Beispiel:

    var doorLockDevice = home.devices().list().first { device -> device.has(DoorLock) }

    val traitFlow: Flow<DoorLock?> =
      doorLockDevice.type(DoorLockDevice).map { it.standardTraits.doorLock }.distinctUntilChanged()

    val doorLockTrait: DoorLock = traitFlow.first()!!

    if (doorLockTrait.supports(DoorLock.Attribute.wrongCodeEntryLimit)) {
      val unused = doorLockTrait.update { setWrongCodeEntryLimit(3u) }
    }

Mehrere Befehle gleichzeitig senden

Mit der Batching API kann ein Client mehrere Home APIs-Gerätebefehle in einer einzigen Nutzlast senden. Die Befehle werden in einer einzigen Payload zusammengefasst und parallel ausgeführt. Das ist vergleichbar mit der Erstellung einer Automatisierung mit der Home API über den parallelen Knoten, z. B. Jalousien vor Sonnenaufgang öffnen. Die Batching API ermöglicht jedoch komplexere und ausgefeiltere Abläufe als die Automation API, z. B. die dynamische Auswahl von Geräten zur Laufzeit nach beliebigen Kriterien.

Die Befehle in einem Batch können auf mehrere Eigenschaften auf mehreren Geräten, in mehreren Räumen und in mehreren Gebäuden ausgerichtet sein.

Wenn Befehle in einem Batch gesendet werden, können Geräte Aktionen gleichzeitig ausführen. Das ist nicht möglich, wenn Befehle nacheinander in separaten Anfragen gesendet werden. Mithilfe von Batch-Befehlen kann der Entwickler den Status einer Gruppe von Geräten so festlegen, dass er einem vordefinierten Gesamtstatus entspricht.

Batch API verwenden

Das Aufrufen von Befehlen über die Batching API umfasst drei grundlegende Schritte:

  1. Rufen Sie die Methode Home.sendBatchedCommands() auf.
  2. Geben Sie im Text des sendBatchedCommands()-Blocks die Befehle an, die in den Batch aufgenommen werden sollen.
  3. Prüfen Sie die Ergebnisse der gesendeten Befehle, um festzustellen, ob sie erfolgreich waren oder fehlgeschlagen sind.

Methode „sendBatchedCommands()“ aufrufen

Rufen Sie die Methode Home.sendBatchedCommands() auf. Im Hintergrund richtet diese Methode einen Lambda-Ausdruck in einem speziellen Batch-Kontext ein.

home.sendBatchedCommands() {

Batchbefehle angeben

Fügen Sie im Body des sendBatchedCommands()-Blocks Batchbefehle ein. Batchbefehle sind „Schatten“-Versionen vorhandener Device API-Befehle, die in einem Batchkontext verwendet werden können. Sie werden mit dem zusätzlichen Suffix Batchable benannt. Beispielsweise hat der Befehl moveToLevel() für das Merkmal LevelControl ein Pendant namens moveToLevelBatchable().

Beispiel:

  val response1 = add(command1)

  val response2 = add(command2)

Der Batch wird automatisch gesendet, sobald dem Batch-Kontext alle Befehle hinzugefügt wurden und die Ausführung den Kontext verlassen hat.

Antworten werden in DeferredResponse<T>-Objekten erfasst.

Die DeferredResponse<T>-Instanzen können in einem Objekt beliebigen Typs zusammengefasst werden, z. B. in einem Collection oder einer von Ihnen definierten Datenklasse. Der von sendBatchedCommands() zurückgegebene Objekttyp ist derjenige, den Sie zum Zusammenstellen der Antworten ausgewählt haben. Der Batch-Kontext kann beispielsweise zwei DeferredResponse-Instanzen in einer Pair zurückgeben:

  val (response1, response2) = homeClient.sendBatchedComamnds {
    val response1 = add(someCommandBatched(...))
    val response2 = add(someOtherCommandBatched(...))
    Pair(response1, response2)
  }

Alternativ können die DeferredResponse-Instanzen im Batchkontext in einer benutzerdefinierten Datenklasse zurückgegeben werden:

  // Custom data class
  data class SpecialResponseHolder(
    val response1: DeferredResponse<String>,
    val response2: DeferredResponse<Int>,
    val other: OtherResponses
  )
  data class OtherResponses(...)

Jede Antwort prüfen

Prüfen Sie außerhalb des sendBatchedCommands()-Blocks die Antworten, um festzustellen, ob der entsprechende Befehl erfolgreich war oder fehlgeschlagen ist. Dazu wird DeferredResponse.getOrThrow() aufgerufen. Das gibt entweder das Ergebnis des ausgeführten Befehls zurück oder löst einen Fehler aus, wenn der Batch-Bereich nicht abgeschlossen wurde oder der Befehl fehlgeschlagen ist.

Sie sollten die Ergebnisse nur außerhalb des sendBatchedCommands()-Lambda-Scopes prüfen.

Beispiel

Angenommen, Sie möchten eine App erstellen, mit der Sie über die Batching API eine Szene für „Gute Nacht“ einrichten, die alle Geräte im Zuhause für die Nacht konfiguriert, wenn alle schlafen. Diese App sollte das Licht ausschalten und die Vorder- und Hintertüren verriegeln.

So könnten Sie vorgehen:

val lightDevices: List<OnOffLightDevice>
val doorlockDevices: List<DoorLockDevice>

// Send all the commands
val responses: List<DeferredResponse<Unit>> = home.sendBatchedCommands {
  // For each light device, send a Batchable command to turn it on
  val lightResponses: List<DeferredResponse<Unit>> = lightDevices.map { lightDevice ->
    add(lightDevice.standardTraits.onOff.onBatchable())
  }

  // For each doorlock device, send a Batchable command to lock it
  val doorLockResponse: List<DeferredResponse<Unit>> = doorlockDevices.map { doorlockDevice ->
    add(doorlockDevice.standardTraits.doorLock.lockDoorBatchable())
  }

  lightResponses + doorLockResponses
}

// Check that all responses were successful
for (response in responses) {
  response.getOrThrow()
}