Управление устройствами на 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 , которая в модели данных экосистемы 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,
)

Переместить на уровень

// 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 признака «Вкл/Выкл»:

// 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 или схеме smart home Cloud-to-cloud . Для этих атрибутов можно определить, является ли возвращаемое атрибутом значение 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!")
}

Обновление атрибутов черт

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

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

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

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

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

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

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

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

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

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

В этом примере показано, как явно задать значение атрибута 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) }
    }

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

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

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

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

Используйте API пакетной обработки

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

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

Вызовите метод sendBatchedCommands()

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

home.sendBatchedCommands() {

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

В теле блока sendBatchedCommands() заполните пакетные команды. Пакетные команды — это «теневые» версии существующих команд 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() .

Пример

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