Sterowanie urządzeniami z Androidem

Sprawdzanie, czy cecha obsługuje polecenie

Możesz też sprawdzić, czy dana komenda cechy jest obsługiwana. Użyj też funkcji supports na poziomie cechy, aby sprawdzić, czy polecenie jest obsługiwane na danym urządzeniu.

Aby na przykład sprawdzić, czy urządzenie obsługuje polecenie toggle cechy Włączanie/wyłączanie:

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

Wysyłanie polecenia do urządzenia

Wysłanie polecenia jest podobne do odczytania atrybutu stanu z cechy. Aby włączyć lub wyłączyć urządzenie, użyj polecenia OnOff cechy Toggle, które jest zdefiniowane w modelu danych ekosystemu Google Home jako toggle(). Ta metoda zmienia wartość onOff na false, jeśli jest ona równa true, lub na true, jeśli jest równa false:

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

Wszystkie polecenia cech są funkcjami suspend i zostają wykonane dopiero wtedy, gdy interfejs API zwróci odpowiedź (np. potwierdzającą zmianę stanu urządzenia). Jeśli w przepływie wykonania zostanie wykryty problem, polecenia mogą zwrócić wyjątek. Jako deweloper powinieneś używać bloku try-catch, aby prawidłowo obsługiwać te wyjątki i wyświetlać użytkownikom szczegółowe informacje w przypadkach, w których błędy wymagają podjęcia działań. Nieobsłużone wyjątki zatrzymają działanie aplikacji i mogą spowodować jej awarię.

Możesz też użyć poleceń off() lub on(), aby jednoznacznie ustawić stan:

onOffTrait.off()
onOffTrait.on()

Po wysłaniu polecenia zmiany stanu, po jego zakończeniu, możesz odczytać stan zgodnie z opisem w sekcji Odczyt stanu urządzenia, aby obsłużyć go w swojej aplikacji. Alternatywnie, możesz użyć przepływów opisanych w sekcji Obserwacja stanu, co jest preferowaną metodą.

Wysyłanie polecenia z parametrami

Niektóre polecenia mogą używać parametrów, np. w przypadku cech OnOff lub 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(),
)

Niektóre polecenia mają argumenty opcjonalne, podawane po argumentach wymaganych.

Na przykład polecenie step dla FanControl cechy ma 2 argumenty opcjonalne:

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 }

Sprawdzanie, czy cecha obsługuje atrybut

Niektóre urządzenia mogą obsługiwać cechę Matter, ale nie konkretny atrybut. Na przykład urządzenie Cloud-to-cloud, które zostało przypisane do Matter, może nie obsługiwać wszystkich atrybutów Matter. Aby obsługiwać takie przypadki, użyj funkcji supports na poziomie cechy i wyliczenia Attribute cechy, aby sprawdzić, czy atrybut jest obsługiwany na danym urządzeniu.

Aby na przykład sprawdzić, czy urządzenie obsługuje atrybut onOff cechy Włączanie/wyłączanie:

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

Niektóre atrybuty mogą mieć wartość null w specyfikacji Matter lub w schemacie Cloud-to-cloud smart home. W przypadku tych atrybutów możesz określić, czy wartość null zwrócona przez atrybut wynika z tego, że urządzenie nie zgłasza tej wartości, czy wartość atrybutu to w rzeczywistości null. Aby to zrobić, użyj isNullable oprócz supports:

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

Aktualizowanie atrybutów cech

Jeśli chcesz zmienić wartość danego atrybutu, a żadne z poleceń cechy tego nie robi, atrybut może obsługiwać jawne ustawienie wartości.

Możliwość zmiany wartości atrybutu zależy od 2 czynników:

  • Czy atrybut można zapisywać?
  • Czy wartość atrybutu może się zmienić w wyniku wysłania polecenia dotyczącego cechy?

Informacje te znajdziesz w dokumentacji referencyjnej dotyczącej cech i ich atrybutów.

Kombinacje właściwości, które określają, jak wartość atrybutu może zostać zmieniona, to:

  • Tylko do odczytu i nie ma na niego wpływu inne polecenia. Oznacza to, że wartość atrybutu się nie zmienia. Na przykład atrybut currentPosition cechy Switch.

  • Tylko do odczytu i podatne na inne polecenia. Oznacza to, że wartość atrybutu może ulec zmianie tylko w wyniku wysłania polecenia. Na przykład atrybut currentLevel cechy LevelControl Matter jest tylko do odczytu, ale jego wartość można zmienić za pomocą poleceń takich jak moveToLevel.

  • Można je zapisywać i nie mają na nie wpływu inne polecenia. Oznacza to, że możesz bezpośrednio zmienić wartość atrybutu, korzystając z funkcji update cechy, ale nie ma poleceń, które miałyby wpływ na wartość atrybutu. Na przykład atrybut WrongCodeEntryLimit cechy DoorLock.

  • Można w nich zapisywać dane i mają na nie wpływ inne polecenia. Oznacza to, że możesz bezpośrednio zmienić wartość atrybutu za pomocą funkcji update cechy, a wartość atrybutu może się zmienić w wyniku wysłania polecenia. Na przykład do atrybutu speedSetting elementu FanControlTrait można zapisać bezpośrednio, ale można go też zmieniać za pomocą polecenia step.

Przykład użycia funkcji aktualizacji do zmiany wartości atrybutu

Ten przykład pokazuje, jak jawnie ustawić wartość DoorLockTrait.WrongCodeEntryLimit atrybutu.

Aby ustawić wartość atrybutu, wywołaj funkcję update cechy i przekaż jej funkcję mutatora, która ustawi nową wartość. Dobrą praktyką jest najpierw sprawdzenie, czy cecha obsługuje atrybut.

Na przykład:

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

Wysyłanie wielu poleceń jednocześnie

Interfejs API przetwarzania wsadowego umożliwia klientowi wysyłanie wielu poleceń urządzeń Home API w jednym ładunku. Polecenia są grupowane w pojedynczy ładunek i wykonywane równolegle, podobnie jak można by utworzyć automatyzację interfejsu Home API przy użyciu węzła równoległego, jak w przykładzie Otwieranie żaluzji przed wschodem słońca. Jednakże API przetwarzania wsadowego umożliwia bardziej złożone i zaawansowane zachowania niż API automatyzacji, np. możliwość dynamicznego wybierania urządzeń w czasie wykonywania według dowolnych kryteriów.

Polecenia w jednej partii mogą dotyczyć wielu cech na wielu urządzeniach, w wielu pomieszczeniach, w wielu strukturach.

Wysyłanie poleceń w partiach pozwala urządzeniom wykonywać działania jednocześnie, co nie jest możliwe w przypadku wysyłania poleceń sekwencyjnie w oddzielnych żądaniach. Działanie osiągane za pomocą poleceń wsadowych umożliwia deweloperowi ustawienie stanu grupy urządzeń tak, aby odpowiadał on z góry określonym stanom zbiorczym.

Użyj interfejsu API przetwarzania wsadowego

Wywoływanie poleceń za pomocą interfejsu API przetwarzania wsadowego składa się z trzech podstawowych kroków:

  1. Wywołaj metodę Home.sendBatchedCommands().
  2. W treści bloku sendBatchedCommands() określ polecenia, które mają zostać uwzględnione w partii.
  3. Sprawdź wyniki wysłanych poleceń, aby dowiedzieć się, czy zostały wykonane prawidłowo.

Wywołaj metodę sendBatchedCommands()

Wywołaj metodę Home.sendBatchedCommands(). W tle ta metoda konfiguruje wyrażenie lambda w specjalnym kontekście wsadowym.

home.sendBatchedCommands() {

Określ polecenia wsadowe

W treści bloku sendBatchedCommands() wprowadź polecenia wsadowe. Polecenia wsadowe to „wersje-cienie” istniejących poleceń interfejsu API urządzeń, których można używać w kontekście wsadowym. Do ich nazwy dodawany jest sufiks Batchable. Na przykład polecenie moveToLevel() cechy LevelControl ma odpowiednik o nazwie moveToLevelBatchable().

Przykład:

  val response1 = add(command1)

  val response2 = add(command2)

Partia jest wysyłana automatycznie po dodaniu wszystkich poleceń do kontekstu partii i opuszczeniu tego kontekstu przez wykonanie.

Odpowiedzi są rejestrowane w obiektach DeferredResponse<T>.

Instancje DeferredResponse<T> można zebrać w obiekcie dowolnego typu, takim jak Collection, lub w zdefiniowanej przez Ciebie klasie danych. sendBatchedCommands() zwraca dowolny typ obiektu wybranego do zebrania odpowiedzi. Na przykład kontekst pakietu może zwrócić 2 instancje DeferredResponsePair:

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

Alternatywnie kontekst wsadowy może zwrócić wystąpienia DeferredResponse w niestandardowej klasie danych:

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

Zaznacz każdą odpowiedź

Poza blokiem sendBatchedCommands() sprawdź odpowiedzi, aby określić, czy odpowiednie polecenie zakończyło się powodzeniem, czy nie. Odbywa się to przez wywołanie funkcji DeferredResponse.getOrThrow(), która:<ul><li>zwraca wynik wykonanego polecenia;</li><li>lub, jeśli zakres pakietu nie został ukończony lub polecenie zakończyło się niepowodzeniem, zgłasza błąd.</li></ul>

Należy sprawdzać tylko wyniki poza zakresem lambda sendBatchedCommands().

Przykład

Załóżmy, że chcesz utworzyć aplikację, która korzysta z interfejsu Batching API do skonfigurowania sceny „Dobranoc”, która ustawia wszystkie urządzenia w domu na tryb nocny, gdy wszyscy śpią. Ta aplikacja powinna wyłączać światła i zamykać drzwi wejściowe i tylne.

Oto jeden ze sposobów wykonania tego zadania:

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