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

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

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

Например, чтобы проверить, поддерживает ли устройство команду 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")
}

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

Отправка команды похожа на чтение атрибута состояния из признака. Чтобы включить или выключить устройство, используйте команду Toggle (Переключить) из OnOff trait (черты), которая в модели данных экосистемы 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 trait имеет два необязательных аргумента:

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 признака "Включение/выключение":

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

Некоторые атрибуты могут иметь нулевое значение в спецификации 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 с помощью параллельного узла, например открыли жалюзи до восхода солнца. Однако API для пакетной обработки позволяет выполнять более сложные действия, чем API автоматизации, например динамически выбирать устройства во время выполнения в соответствии с любыми критериями.

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

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

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

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

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

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

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

home.sendBatchedCommands() {

Как задать пакетные команды

В теле блока sendBatchedCommands() заполните команды, которые можно объединять в пакеты. Команды, которые можно объединять в пакеты, – это "теневые" версии существующих команд Device API, которые можно использовать в контексте пакета. Их названия дополнены суффиксом Batchable. Например, у команды LevelControl из черты moveToLevel() есть аналог 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()
}