Как управлять устройствами на Android

Как проверить, поддерживает ли свойство команду

Поддержку также можно проверить для команды свойства. Также используйте функцию supports на уровне признака, чтобы проверить, поддерживается ли команда для определенного устройства.

Например, чтобы проверить, поддерживает ли устройство команду toggle типа On/Off, выполните следующий запрос:

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

Как отправить команду на устройство

Отправка команды похожа на чтение атрибута состояния из типажа. Чтобы включить или выключить устройство, используйте команду Toggle типа OnOff, которая в модели данных экосистемы Google Home определяется как toggle(). Этот метод меняет значение onOff на false, если оно равно true, или на true, если оно равно false:

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

Все команды, связанные с признаками, являются функциями suspend и выполняются только после того, как API возвращает ответ (например, подтверждение изменения состояния устройства). Если в процессе выполнения команды будет обнаружена проблема, может возникнуть исключение. Разработчикам следует использовать блок try-catch, чтобы правильно обрабатывать эти исключения и показывать пользователям подробную информацию о том, как устранить ошибки. Необработанные исключения останавливают работу приложения и могут приводить к сбоям.

Вы также можете использовать команды off() или on(), чтобы явно задать статус:

onOffTrait.off()
onOffTrait.on()

После отправки команды на изменение состояния вы можете прочитать состояние, как описано в разделе Чтение состояния устройства, чтобы обработать его в своем приложении. Также можно использовать потоки, как описано в разделе Наблюдение за состоянием. Это предпочтительный метод.

Как отправить команду с параметрами

В некоторых командах могут использоваться параметры, например в признаках OnOff или 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(),
)

У некоторых команд есть необязательные аргументы, которые указываются после обязательных.

Например, у команды step для свойства FanControl есть два необязательных аргумента:

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 }

Как проверить, поддерживает ли функция атрибут

Некоторые устройства могут поддерживать определенную функцию (Matter), но не поддерживать конкретный атрибут. Например, устройство Cloud-to-cloud, сопоставленное с типом Matter, может не поддерживать все атрибуты Matter. Чтобы обрабатывать такие случаи, используйте функцию supports на уровне типажа и перечисление Attribute типажа, чтобы проверить, поддерживается ли атрибут для определенного устройства.

Например, чтобы проверить, поддерживает ли устройство атрибут onOff типа On/Off, выполните следующий запрос:

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

Некоторые атрибуты могут иметь значение NULL в спецификации Matter или схеме Cloud-to-cloud smart home. Для таких атрибутов вы можете определить, возвращается ли значение null из-за того, что устройство не сообщает значение, или же значение атрибута действительно равно null. Для этого используйте isNullable в дополнение к 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!")
}

Обновить атрибуты признака

Если вы хотите изменить значение определенного атрибута, но ни одна из команд типажа этого не делает, атрибут может поддерживать явную установку своего значения.

Возможность изменить значение атрибута зависит от двух факторов:

  • Доступен ли атрибут для записи?
  • Может ли значение атрибута измениться в результате отправки команды trait?

Эту информацию можно найти в справочной документации по типажам и их атрибутам.

Таким образом, сочетания свойств, определяющие, как может быть изменено значение атрибута, следующие:

  • Доступно только для чтения и не зависит от других команд. Это означает, что значение атрибута не меняется. Например, атрибут currentPosition свойства Switch.

  • Доступно только для чтения и зависит от других команд. Это означает, что значение атрибута может измениться только в результате отправки команды. Например, атрибут currentLevel типа LevelControl Matter доступен только для чтения, но его значение можно изменить с помощью команд, таких как moveToLevel.

  • Доступно для записи и не зависит от других команд. Это означает, что вы можете напрямую изменить значение атрибута, используя функцию update типа, но нет команд, которые повлияют на значение атрибута. Например, атрибут WrongCodeEntryLimit свойства DoorLock.

  • Доступно для записи и зависит от других команд. Это означает, что вы можете напрямую изменить значение атрибута с помощью функции update, а также значение атрибута может измениться в результате отправки команды. Например, атрибут speedSetting объекта FanControlTrait можно задать напрямую, а также изменить с помощью команды step.

Пример использования функции update для изменения значения атрибута

В этом примере показано, как явно задать значение атрибута DoorLockTrait.WrongCodeEntryLimit.

Чтобы задать значение атрибута, вызовите функцию update и передайте ей функцию-мутатор, которая задает новое значение. Рекомендуется сначала проверить, поддерживает ли свойство атрибут.

Пример:

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

Как отправить несколько команд одновременно

Batching API позволяет клиенту отправлять несколько команд Home API для устройств в одной полезной нагрузке. Команды объединяются в один пакет и выполняются параллельно, как при создании автоматизации Home API с помощью параллельного узла, например в сценарии Открыть жалюзи до рассвета. Однако Batching API позволяет выполнять более сложные действия, чем Automation API, например динамически выбирать устройства во время выполнения по любым критериям.

Команды в одном пакете могут быть нацелены на несколько признаков на нескольких устройствах, в нескольких комнатах, в нескольких структурах.

Пакетная отправка команд позволяет устройствам выполнять действия одновременно, что невозможно при последовательной отправке команд в отдельных запросах. Поведение, достигаемое с помощью пакетных команд, позволяет разработчику установить состояние группы устройств в соответствии с заранее определенным агрегированным состоянием.

Как использовать Batching API

Вызов команд через Batching API состоит из трех основных этапов:

  1. Вызовите метод Home.sendBatchedCommands().
  2. В теле блока sendBatchedCommands() укажите команды, которые нужно включить в пакет.
  3. Проверьте результаты отправленных команд, чтобы узнать, были ли они выполнены.

Отправка пакетов команд

Вызовите метод Home.sendBatchedCommands(). Этот метод настраивает лямбда-выражение в специальном контексте пакета.

home.sendBatchedCommands() {

Укажите пакетные команды

Внутри блока sendBatchedCommands() укажите команды, которые можно объединять в пакеты. Команды, которые можно объединять в пакеты, – это "теневые" версии существующих команд Device API, которые можно использовать в контексте пакета. Они имеют суффикс Batchable. Например, у команды moveToLevel() типажа LevelControl есть аналог moveToLevelBatchable().

Пример:

  val response1 = add(command1)

  val response2 = add(command2)

Пакет автоматически отправляется, когда все команды добавлены в контекст пакета и выполнение вышло за пределы контекста.

Ответы записываются в объекты DeferredResponse<T>.

Экземпляры DeferredResponse<T> можно собрать в объект любого типа, например Collection, или в определенный вами класс данных. Тип объекта, который вы выберете для сборки ответов, будет возвращен методом sendBatchedCommands(). Например, контекст пакета может возвращать два экземпляра DeferredResponse в Pair:

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

Кроме того, контекст пакета может возвращать экземпляры DeferredResponse в пользовательском классе данных:

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

Проверяйте каждый ответ

За пределами блока sendBatchedCommands() проверьте ответы, чтобы определить, была ли соответствующая команда выполнена успешно. Для этого вызывается метод DeferredResponse.getOrThrow(), который: - возвращает результат выполненной команды; - или, если область пакетной обработки не завершена или команда не выполнена, выдает ошибку.

Проверять результаты следует только вне области действия лямбда-выражения sendBatchedCommands().

Пример

Предположим, вы хотите создать приложение, которое использует Batching API для настройки сцены "Спокойной ночи", которая настраивает все устройства в доме на ночное время, когда все спят. Это приложение должно выключить свет и запереть переднюю и заднюю двери.

Вот один из способов решения этой задачи:

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