Geräte unter Android steuern

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

Die Unterstützung kann auch für einen Attributbefehl geprüft werden. Verwende außerdem die Funktion supports auf Attributebene, um zu prüfen, ob ein Befehl für ein bestimmtes Gerät unterstützt wird.

So kannst du beispielsweise prüfen, ob ein Gerät den Befehl toggle des Attributs „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 Trait. Wenn du das Gerät ein- oder ausschalten möchtest, verwende den Toggle-Befehl des Attributs OnOff, der im Datenmodell des Google Home-Ökosystems als toggle() definiert ist. Mit dieser Methode wird onOff in false geändert, wenn der Wert true ist, oder in true, wenn der Wert false ist:

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

Alle Befehle für Eigenschaften sind suspend-Funktionen und werden erst abgeschlossen, wenn die API eine Antwort zurückgibt (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 verarbeiten und Nutzern detaillierte Informationen zu Fällen zu liefern, in denen die Fehler behoben werden können. Unbehandelte Ausnahmen stoppen die App-Laufzeit und können zu Abstürzen in Ihrer App führen.

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

onOffTrait.off()
onOffTrait.on()

Nachdem du einen Befehl zum Ändern des Status gesendet hast, kannst du den Status wie unter Gerätestatus lesen beschrieben auslesen, um ihn in deiner App zu verarbeiten. Alternativ kannst du Abläufe wie unter Status beobachten beschrieben verwenden. Dies ist die bevorzugte Methode.

Befehl mit Parametern senden

Einige Befehle können Parameter verwenden, z. B. die in den Attributen 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 angegeben werden.

Der step-Befehl für das FanControl-Attribut 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 Attribut von einer Eigenschaft unterstützt wird

Einige Geräte unterstützen möglicherweise ein Matter-Trait, aber kein bestimmtes Attribut. Beispielsweise unterstützt ein Cloud-to-cloud-Gerät, das Matter zugeordnet wurde, möglicherweise nicht alle Matter-Attribute. Verwende in solchen Fällen die Funktion supports auf Trait-Ebene und das Enum Attribute des Traits, um zu prüfen, ob das Attribut für ein bestimmtes Gerät unterstützt wird.

So kannst du beispielsweise prüfen, ob ein Gerät das Attribut onOff des Merkmals „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 können in der Matter-Spezifikation oder im Cloud-to-cloud-smart home-Schema null sein. Bei diesen Attributen können Sie feststellen, ob ein vom Attribut zurückgegebener Nullwert darauf zurückzuführen ist, dass das Gerät diesen Wert nicht meldet, oder ob der Wert des Attributs tatsächlich null ist. Verwenden Sie dazu zusätzlich zu supports den Wert isNullable:

// 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!")
}

Eigenschaftsattribute aktualisieren

Wenn du den Wert eines bestimmten Attributs ändern möchtest und keiner der Befehle des Attributs dies tut, kann der Wert des Attributs möglicherweise explizit festgelegt werden.

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 Nebenwirkung des Sendens eines Attributbefehls ändern?

Diese Informationen finden Sie in der Referenzdokumentation für Eigenschaften und ihre Attribute.

Daher sind die Kombinationen von Attributen, die bestimmen, wie der Wert eines Attributs geändert werden kann, folgende:

  • 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 der Wert des Attributs nur durch das Senden eines Befehls geändert werden kann. Beispielsweise ist das Attribut currentLevel des Merkmals LevelControl Matter schreibgeschützt, aber sein Wert kann durch Befehle wie moveToLevel geändert werden.

  • Beschreibbar und nicht von anderen Befehlen betroffen. Das bedeutet, dass du den Wert des Attributs direkt mit der Funktion update des Merkmals ändern kannst, es aber keine Befehle gibt, die sich auf den Wert des Attributs auswirken. Beispiel: das Attribut WrongCodeEntryLimit des Merkmals DoorLock.

  • Beschreibbar und von anderen Befehlen betroffen. Das bedeutet, dass du den Wert des Attributs direkt mit der Funktion update des Merkmals ändern kannst. Außerdem kann sich der Wert des Attributs ändern, wenn ein Befehl gesendet wird. Beispielsweise kann das Attribut speedSetting von FanControlTrait direkt geschrieben werden, ist aber auch mit dem Befehl step änderbar.

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 du einen Attributwert festlegen möchtest, rufe die update-Funktion des Traits auf und übergib ihr eine Mutatorfunktion, die den neuen Wert festlegt. Es empfiehlt sich, zuerst zu prüfen, ob das Attribut vom Merkmal unterstützt wird.

Beispiel:

    val 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 Nutzlast zusammengefasst und parallel ausgeführt, ähnlich wie bei einer Automatisierung der Home API mit dem parallelen Knoten, z. B. im Beispiel Jalousien vor Sonnenaufgang öffnen. Die Batching API ermöglicht jedoch komplexere und anspruchsvollere Verhaltensweisen als die Automation API, z. B. die Möglichkeit, Geräte zur Laufzeit nach beliebigen Kriterien dynamisch auszuwählen.

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

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. Mit dem Verhalten, das durch Batchbefehle erreicht wird, kann der Entwickler den Status einer Gruppe von Geräten so festlegen, dass er einem vordefinierten aggregierten Status entspricht.

Batching 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.

Batchbefehle senden

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

home.sendBatchedCommands() {

Batchbefehle angeben

Füllen Sie im Text des sendBatchedCommands()-Blocks batchfähige Befehle ein. Batchfähige Befehle sind „Schattenversionen“ vorhandener Device API-Befehle, die in einem Batchkontext verwendet werden können. Sie werden mit dem Suffix Batchable benannt. Beispielsweise hat der Befehl moveToLevel() des Attributs LevelControl ein Gegenstück namens moveToLevelBatchable().

Beispiel:

  val response1 = add(command1)

  val response2 = add(command2)

Der Batch wird automatisch gesendet, sobald alle Befehle dem Batchkontext 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 eines beliebigen Typs zusammengefasst werden, z. B. in einem Collection oder einer von Ihnen definierten Datenklasse. Unabhängig davon, welchen Objekttyp Sie zum Zusammenstellen der Antworten auswählen, wird dieser von sendBatchedCommands() zurückgegeben. Der Batchkontext kann beispielsweise zwei DeferredResponse-Instanzen in einem Pair zurückgeben:

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

Alternativ kann der Batchkontext die DeferredResponse-Instanzen in einer benutzerdefinierten Datenklasse zurückgeben:

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

Antworten 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. Dadurch wird entweder: - das Ergebnis des ausgeführten Befehls zurückgegeben, - oder ein Fehler ausgegeben, wenn der Batchbereich nicht abgeschlossen wurde oder der Befehl nicht erfolgreich war.

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

Beispiel

Angenommen, du möchtest eine App erstellen, die die Batching API verwendet, um eine „Gute Nacht“-Szene einzurichten, die alle Geräte im Zuhause für die Nacht konfiguriert, wenn alle schlafen. Diese App soll das Licht ausschalten und die Vorder- und Hintertür verriegeln.

So können Sie die Aufgabe angehen:

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

Gerätegruppe steuern

Zusätzlich zur detaillierten Steuerung jedes Mitglieds einer Gruppe durch Iterieren durch die Mitgliedsgeräte können Sie eine DeviceGroup als einzelne Entität steuern. DeviceGroup implementiert die Schnittstelle PartsProvider, die die von den Geräten in der Gruppe unterstützten DeviceType-Teile verfügbar macht:

  • group.parts() gibt eine Flow<Set<DeviceType>> aller Gerätetypen in der Gruppe zurück.
  • group.part() und group.partOrNull() geben einen Ablauf für einen bestimmten Gerätetyp zurück, z. B. OnOffLightDevice oder DimmableLightDevice.

Wenn du einen Trait-Befehl für einen Gerätetyp aufrufst, der Teil einer DeviceGroup ist, leitet das SDK den Befehl an alle zugehörigen Mitgliedsgeräte in der Gruppe weiter, die diese Funktion unterstützen, unabhängig von ihrer zugrunde liegenden Verbindung oder ihrem Transport (z. B. Matter oder Cloud-to-cloud).

Gruppenattribute für Teileigenschaften und Fehlerbehandlung

Die Ausrichtung auf einen Gruppenteil vereinfacht gängige Fälle, in denen Sie einen idempotenten Befehl (z. B. das Ein- oder Ausschalten von Lichtern oder das Festlegen einer Helligkeitsstufe) an eine Gruppe senden möchten. Beachte bei der Arbeit mit Attributen und Befehlen für einen DeviceGroup-Teil Folgendes:

  • Dynamische Statusattribute (z. B. das Attribut onOff des Attributs OnOff oder das Attribut currentLevel des Attributs LevelControl) werden im Allgemeinen nicht für das Attribut eines Gruppenmitglieds ausgefüllt, da sich die Mitgliedsgeräte in einer Gruppe in verschiedenen Status befinden können, die nicht zu einem einzigen Wert kombiniert werden können. Unterstützte Befehle (mit supports() geprüft, z. B. LevelControl.Command.MoveToLevelWithOnOff bei einer dimmbaren Lampe) und ausgewählte Funktionsattribute (z. B. colorTempPhysicalMinMireds und colorTempPhysicalMaxMireds bei ColorControl für farbige Lampen) werden jedoch ausgefüllt, damit deine App die UI-Steuerelemente entsprechend konfigurieren kann.
  • Nicht alle Befehle für Eigenschaften von Gruppenmitgliedern sind für einen Gruppenteil verfügbar. Insbesondere sind Befehle, die Werte zurückgeben, nicht verfügbar.
  • Ein Befehl für einen Gruppenteil gibt keine einzelnen Ergebnisse für jeden zugrunde liegenden Befehl für ein Mitgliedsgerät zurück. Wenn Sie einzelne Befehlsergebnisse prüfen oder Befehle aufrufen möchten, die Werte zurückgeben, iterieren Sie direkt durch die Mitgliedsgeräte der Gruppe.
  • Befehle, die an einen Teil eines DeviceGroup gesendet werden, werden abgeschlossen, sobald das gesamte Fanout auf den Mitgliedsgeräten abgeschlossen ist und alle zugrunde liegenden Befehlserfolge oder -fehler erfasst wurden. Ein Befehl schlägt nur dann fehl (löst einen HomeException aus), wenn er für jedes verknüpfte Mitgliedsgerät fehlschlägt oder wenn alle Zielgeräte nicht verfügbar sind. Dies wird als einzelner Befehlsfehler angezeigt, der der Syntax für den Aufruf eines einzelnen Befehls entspricht. Wenn mindestens ein Mitgliedsgerät den Befehl ausführt, wird der Gruppenbefehl ohne Ausnahme abgeschlossen.

Befehle an einen Teil einer Gruppe senden

So schaltest du alle OnOffLightDevice-Lampen in einer Gerätegruppe ein:

val lightPart = group.partOrNull(OnOffLightDevice).firstOrNull()
val onOffTrait = lightPart?.trait(OnOff)

if (onOffTrait?.supports(OnOff.Command.On) == true) {
  onOffTrait.on()
}

Du kannst auch alle Gerätetypen in einer Gruppe mit group.parts() prüfen, um ein Merkmal auf mehrere verwandte Gerätetypen anzuwenden (z. B. alle Lichtgerätetypen in der Gruppe ausschalten, die OnOff unterstützen):

val lightTypes = listOf(
  OnOffLightDevice,
  DimmableLightDevice,
  ColorTemperatureLightDevice,
  ExtendedColorLightDevice,
)

val parts = group.parts().first()
val onOffTrait = lightTypes.firstNotNullOfOrNull { type ->
  parts.firstOrNull { it.factory == type }
}?.trait(OnOff)

onOffTrait?.off()

Du kannst die Befehlsunterstützung prüfen oder die Funktionsgrenzen eines Gruppenparts abrufen, bevor du einen Befehl sendest. Du kannst beispielsweise MoveToLevelWithOnOff für einen dimmbaren Lichtpart prüfen oder colorTempPhysicalMinMireds und colorTempPhysicalMaxMireds für einen Farbtemperatur-Lichtpart abrufen:

val dimmableLight = group.partOrNull(DimmableLightDevice).firstOrNull()
val levelControl = dimmableLight?.trait(LevelControl)

if (
  levelControl?.supports(
    LevelControl.Command.MoveToLevelWithOnOff
  ) == true
) {
  levelControl.moveToLevelWithOnOff(
    level = 127u,
    transitionTime = null,
    optionsMask = LevelControlTrait.OptionsBitmap(),
    optionsOverride = LevelControlTrait.OptionsBitmap(),
  )
}

val colorLight = group.partOrNull(ColorTemperatureLightDevice).firstOrNull()
val colorControl = colorLight?.trait(ColorControl)
val minMireds = colorControl?.colorTempPhysicalMinMireds
val maxMireds = colorControl?.colorTempPhysicalMaxMireds

if (colorControl != null && minMireds != null && maxMireds != null) {
  val midMireds = ((minMireds.toInt() + maxMireds.toInt()) / 2).toUShort()
  colorControl.moveToColorTemperature(
    colorTemperatureMireds = midMireds,
    transitionTime = 0u,
    optionsMask = ColorControlTrait.OptionsBitmap(),
    optionsOverride = ColorControlTrait.OptionsBitmap(),
  )
}