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

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

В спецификации Matter или схеме smart home Cloud-to-cloud некоторые атрибуты допускают значение null. Для таких атрибутов можно определить, связано ли возвращаемое значение 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() укажите команды, доступные для пакетной обработки. Команды, доступные для пакетной обработки, являются «теневыми» версиями существующих команд 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() .

Пример

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