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 Toggle (Przełącz) cechy OnOff, 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 i jego wykonaniu możesz odczytać stan zgodnie z opisem w sekcji Odczytywanie stanu urządzenia, aby obsłużyć go w aplikacji. Możesz też użyć przepływów zgodnie z opisem w sekcji Obserwowanie stanu, która jest preferowaną metodą.

Wysyłanie polecenia z parametrami

Niektóre polecenia mogą używać parametrów, np. w przypadku tych cech:OnOff lubLevelControl:

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, które występują po argumentach wymaganych.

Na przykład polecenie step dla FanControlcechy 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 jest spowodowana tym, ż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.

Czy wartość atrybutu można zmienić, 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 żadne inne polecenie. Oznacza to, że wartość atrybutu nie zmienia się. Na przykład atrybut currentPosition cechy Switch.

  • Tylko do odczytu i podatne na inne polecenia. Oznacza to, że wartość atrybutu może się zmienić tylko w wyniku wysłania polecenia. Na przykład atrybut currentLevel cechy LevelControl Matter jest tylko do odczytu, ale jego wartość można zmieniać 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 za pomocą funkcji update cechy, ale nie ma poleceń, które wpłyną na wartość atrybutu. Na przykład atrybut WrongCodeEntryLimit cechy DoorLock.

  • Można w nim zapisywać dane i jest on modyfikowany przez 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 atrybut occupiedCoolingSetpoint cechy Thermostat można zapisać, ale też zaktualizować za pomocą setpointRaiseLower.

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

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

Aby ustawić wartość atrybutu, wywołaj funkcję update cechy i przekaż jej funkcję mutatora, która ustawia nową wartość. Najpierw sprawdź, czy cecha obsługuje atrybut.

Na przykład:

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

Wysyłanie wielu poleceń jednocześnie

Interfejs Batching API umożliwia klientowi wysyłanie wielu poleceń interfejsów Home API w jednym pakiecie. Polecenia są łączone w jeden pakiet i wykonywane równolegle, podobnie jak w przypadku tworzenia automatyzacji w interfejsie Home API za pomocą węzła równoległego, np. w przykładzie Otwórz rolety przed wschodem słońca. Interfejs Batching API umożliwia jednak bardziej złożone i zaawansowane działania niż interfejs Automation API, np. dynamiczne wybieranie urządzeń w czasie działania programu według dowolnych kryteriów.

Polecenia w jednej partii mogą być kierowane do wielu cech na wielu urządzeniach, w wielu pomieszczeniach i w wielu budynkach.

Wysyłanie poleceń w pakietach umożliwia urządzeniom wykonywanie działań jednocześnie, co nie jest możliwe, gdy polecenia są wysyłane sekwencyjnie w oddzielnych żądaniach. Działanie osiągane za pomocą poleceń wsadowych umożliwia deweloperowi ustawienie stanu grupy urządzeń tak, aby pasował do z góry określonego stanu zbiorczego.

Korzystanie z interfejsu Batching API

Wywoływanie poleceń za pomocą interfejsu Batching API obejmuje 3 podstawowe czynności:

  1. Wywołaj metodę Home.sendBatchedCommands().
  2. W treści bloku sendBatchedCommands() określ polecenia, które mają być 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ślanie poleceń wsadowych

W treści bloku sendBatchedCommands() umieść polecenia, które można wykonywać w pakietach. Polecenia wsadowe to „wersje cieniowe” istniejących poleceń interfejsu Device API, które można stosować w kontekście wsadowym. Ich nazwy mają dodatkowy sufiks Batchable. Na przykład polecenie LevelControl cechy moveToLevel() 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, np. w Collection lub w zdefiniowanej przez Ciebie klasie danych. Typ obiektu, z którego składasz odpowiedzi, jest zwracany przez sendBatchedCommands(). Na przykład kontekst wsadowy może zwrócić 2 instancje DeferredResponsePair:

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

Kontekst wsadowy może też zwracać instancje 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(...)

Sprawdzanie każdej odpowiedzi

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>

Wyniki należy sprawdzać tylko poza zakresem funkcji lambda sendBatchedCommands().

Przykład

Załóżmy, że chcesz utworzyć aplikację, która korzysta z interfejsu Batching API do skonfigurowania sceny „Dobranoc”, która konfiguruje wszystkie urządzenia w domu na noc, 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()
}