Sterowanie urządzeniami z Androidem

Sprawdzanie, czy cecha obsługuje polecenie

Możesz też sprawdzić, czy cecha obsługuje polecenie. 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 cechy On/Off: toggle

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

Wysyłanie polecenia jest podobne do odczytywania atrybutu stanu z cechy. Aby włączyć lub wyłączyć urządzenie, użyj OnOff polecenia Toggle cechy, 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ą suspend funkcjami i kończą się tylko wtedy, gdy interfejs API zwróci odpowiedź (np. potwierdzającą zmianę stanu urządzenia). Polecenia mogą zwracać wyjątek, jeśli podczas wykonywania wystąpi problem. Jako programista musisz użyć bloku try-catch, aby prawidłowo obsługiwać te wyjątki i wyświetlać użytkownikom szczegółowe informacje w przypadkach, gdy błędy można naprawić. 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ługiwać 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. tych z OnOff lub LevelControl cech:

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 cechy FanControl trait 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 zmapowane na Matter, może nie obsługiwać wszystkich Matter atrybutów. 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 cechy On/Off: onOff

// 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 też wartość atrybutu jest rzeczywiście równa null. Aby to zrobić, użyj funkcji isNullable oprócz funkcji 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 cechy

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

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

  • Czy atrybut jest zapisywalny?
  • Czy wartość atrybutu może się zmienić jako efekt uboczny wysłania polecenia cechy?

Te informacje znajdziesz w dokumentacji referencyjnej dotyczącej cech i ich atrybutów.

Dlatego kombinacje właściwości, które określają, jak można zmienić wartość atrybutu, to:

  • Tylko do odczytu i nie mające wpływu na inne polecenia. Oznacza to, że wartość atrybutu nie zmienia się. Na przykład atry currentPositionbut atrybutu Switch cechy.

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

  • Zapisywalne i nie mające wpływu na 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 trait.

  • Zapisywalne i mające wpływ na 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 speedSetting FanControlTrait można bezpośrednio zapisać, ale można go też zmienić za pomocą polecenia step.

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

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

Aby ustawić wartość atrybutu, wywołaj funkcję update cechy i przekaż jej funkcję mutatora, która ustawia nową wartość. Dobrym rozwiązaniem 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 kilku poleceń naraz

Interfejs Batching API umożliwia klientowi wysyłanie wielu poleceń do interfejsów Home API w jednym ładunku. Polecenia są grupowane w jeden ładunek i wykonywane równolegle, podobnie jak w przypadku tworzenia automatyzacji Home API automatyzacji za pomocą węzła równoległego, np. w przykładzie Otwieranie rolet przed wschodem słońca . Interfejs Batching API umożliwia jednak bardziej złożone i zaawansowane zachowania niż interfejs Automation API, np. możliwość dynamicznego wybierania urządzeń w czasie działania według dowolnych kryteriów.

Polecenia w jednej partii mogą być kierowane na wiele cech na różnych urządzeniach, w różnych pokojach i w różnych strukturach.

Wysyłanie poleceń w partii umożliwia urządzeniom wykonywanie działań jednocześnie, co nie jest możliwe, gdy polecenia są wysyłane sekwencyjnie w osobnych żądaniach. Zachowanie osiągane za pomocą poleceń w partii umożliwia programiście ustawienie stanu grupy urządzeń tak, aby odpowiadał on z góry określonym stanom zbiorczym.

Korzystanie z interfejsu Batching API

Aby wywołać polecenia za pomocą interfejsu Batching API, musisz wykonać 3 podstawowe czynności:

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

Wywoływanie metody sendBatchedCommands()

Wywołaj metodę Home.sendBatchedCommands(). Za kulisami ta metoda konfiguruje wyrażenie lambda w specjalnym kontekście partii.

home.sendBatchedCommands() {

Określanie poleceń partii

W treści bloku sendBatchedCommands() wypełnij polecenia w partii. Polecenia w partii to „cienie” istniejących poleceń Device API, których można używać w kontekście partii. Ich nazwy mają dodany sufiks Batchable. Na przykład cechy ma odpowiednik o nazwie moveToLevelBatchable().LevelControlmoveToLevel()

Przykład:

  val response1 = add(command1)

  val response2 = add(command2)

Partia jest wysyłana automatycznie, gdy wszystkie polecenia zostaną dodane do kontekstu partii, a wykonanie opuści ten kontekst.

Odpowiedzi są przechwytywane w DeferredResponse<T> obiektach.

Instancje DeferredResponse<T> można zebrać w obiekt dowolnego typu, np. Collection, lub zdefiniowaną przez Ciebie klasę danych. Typ obiektu, w którym zdecydujesz się zebrać odpowiedzi, jest zwracany przez sendBatchedCommands(). Na przykład kontekst partii może zwrócić 2 DeferredResponse instancje w Pair:

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

Alternatywnie, kontekst partii może zwrócić DeferredResponse instancje 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 zostało wykonane pomyślnie. Aby to zrobić, wywołaj DeferredResponse.getOrThrow(), które albo: - zwraca wynik wykonanego polecenia, - albo, jeśli zakres partii nie został ukończony lub polecenie się nie powiodło, zgłasza błąd.

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

Przykład

Załóżmy, że chcesz utworzyć aplikację, która używa interfejsu Batching API do skonfigurowania sceny „dobranoc”, która konfiguruje wszystkie urządzenia w domu na noc, gdy wszyscy śpią. Ta aplikacja powinna wyłączyć światła i zamknąć drzwi wejściowe i tylne.

Oto jeden ze sposobów podejścia do 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()
}