Structure API на Android

Доступ к Structure API можно получить через Home API для Android. Импортируйте в приложение следующие пакеты:

import com.google.home.Home
import com.google.home.Id
import com.google.home.Structure

Обработка ошибок

Любой метод в Home API может выдать ошибку HomeException, поэтому мы рекомендуем использовать блок try-catch, чтобы отлавливать HomeException во всех вызовах.

При обработке HomeException проверьте поля error.code и error.message, чтобы узнать, что пошло не так. Также могут быть дополнительные коды ошибок, поэтому вызовите метод getSubErrorCodes() и проверьте результат.

Если исключение не будет обработано, приложение завершит работу со сбоем.

Подробнее об обработке ошибок…

Примеры звонков

Как получить список домов

После инициализации вызов structures() возвращает поток доступных вам структур:

// Get a flow of all structures accessible to the user
val allStructuresFlow: HomeObjectsFlow<Structure> = home.structures()

// Calling list() on a HomeObjectsFlow returns the first Set of elements.
val allStructures: Set<Structure> = allStructuresFlow.list()

structures() API – это поток, который может не сразу возвращать действительный список структур. Если ваше приложение реактивно и подписывается на этот поток для управления пользовательским интерфейсом, в конечном итоге должен быть возвращен действительный список структур. Пустой список структур может быть возвращен и в других ситуациях, например если телефон пользователя потеряет связь или если пользователь отзовет разрешения для вашего приложения. Обязательно предусмотрите обработку таких случаев в своем приложении.

Если же вам нужно использовать императивное программирование вместо реактивного, можно применить терминальный оператор потока:

val everyStructure = withTimeout(5000) { home.structures().first { it.isNotEmpty() } }

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

Получение свойств структуры

Получив список структур, вы можете получить доступ к их свойствам:

// Get a flow on a structure. Flow emits new values on structure metadata changes: name.
val structureFlow: Flow<Structure> = home.structures().itemFlow(myStructureId)

// Get a snapshot of the structure.
val structure: Structure = structureFlow.first()

// Get structure properties
println("id ${structure.id}")
println("name ${structure.name}")

Как найти здание по названию

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

val myHome = home.structures().list().first { it.name == "My home" }

Там вы найдете информацию о домах, комнатах и устройствах.

Как работать с несколькими структурами

Чтобы использовать несколько структур, получите отдельную ссылку на каждую из них:

var structure1: Structure? = null
var structure2: Structure? = null

try {
  structure1 = home.structures().list().firstOrNull { it.name == "Main House" }
} catch (e: HomeException) {
  // Code for handling the exception
}
try {
  structure2 = home.structures().list().firstOrNull { it.name == "Guest Cottage" }
} catch (e: HomeException) {
  // Code for handling the exception
}

Как получить список комнат

Получив структуру, вы можете получить список комнат и доступ к их свойствам:

val allRoomsFlow: HomeObjectsFlow<Room> = structure.rooms()
val allRooms: Set<Room> = allRoomsFlow.list()
val room: Room = allRooms.first()

println("id ${room.id}")
println("name ${room.name}")

Создать чат-комнату

Чтобы создать чат-группу:

val testName = "Test Room Name"
val newRoom: Room = structure.createRoom(testName)

Как удалить комнату

Вы также можете удалить комнату. Для этого:

val roomToDelete = structure.rooms().list().filter { it.name == "room_id1" }.firstOrNull()
    structure.deleteRoom(roomToDelete!!)

Вы также можете удалить чат-группу, указав только ее идентификатор:

val roomToDelete1 = allRooms.filter { it.id == testRoomId }.firstOrNull()
structure.deleteRoom(roomToDelete1!!)

Если удалить комнату, в которой есть устройства, они останутся в доме, но не будут назначены ни одной комнате.

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

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

val room2 = structure.rooms().get(Id("room_id_other_structure"))
    val device1 = structure.devices().get(Id("device_id1"))
    structure.moveDevicesToRoom(room2!!, listOf(device1!!))

Если у вас есть только идентификаторы устройств и комнат, вы также можете перемещать устройства:

structure.moveDevicesToRoom(Id("room_id_other_structure"), listOf(Id("device_id1")))

Как изменить название комнаты

Чтобы изменить название комнаты, вызовите метод setName().

livingRoom.setName("Living Room")

Названия, превышающие ограничение в 60 кодовых точек Unicode (символов), будут усечены без появления ошибок. Разработчики сами решают, как поступать с длинными названиями. Например, они могут предупреждать пользователей о том, что название будет усечено.

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

Чтобы определить, предоставил ли пользователь разрешение на доступ к конфиденциальному или ограниченному типу устройства, используйте функцию consentedDeviceTypes() на уровне структуры:

import com.google.home.Structure
import com.google.home.DeviceType
import com.google.home.DeviceTypeFactory
import com.google.home.consentedDeviceTypes // Extension function from the SDK
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch

/**
 * Example of how an app may monitor which device types have been granted access by a user.
 */
fun monitorDeviceConsent(structure: Structure, myScope: CoroutineScope) {
    // Obtain the flow of consented device type factories
    val consentedTypesFlow: Flow<Set<DeviceTypeFactory<out DeviceType>>> =
        structure.consentedDeviceTypes()

    myScope.launch {
        consentedTypesFlow.collect { consentedSet ->
            // Check if the user has consented to share a specific restricted
            // type, such as a Doorbell or Camera.
            val hasCameraAccess = consentedSet.any {
                it.toString() == "matter.google.type.GoogleDoorbellDevice"
            }

            if (hasCameraAccess) {
                // Enable features that require camera access
            } else {
                // Inform the user or disable camera-specific features
            }
        }
    }
}

Группы устройств

Группа устройств – это определенная пользователем коллекция устройств в рамках одной структуры. Участие в группе сохраняется в сервисах Google и видно другим участникам структуры и другим приложениям, у которых есть доступ к структуре.

Как и физические комнаты, группы устройств принадлежат к одной структуре. Но в отличие от комнат, группы устройств – это логические группы. Устройство может одновременно принадлежать к нескольким группам устройств. Группа устройств может содержать до 100 устройств.

Группы устройств представлены объектом DeviceGroup, который отличается от HomeDevice. Чтобы запрашивать информацию о группах устройств и взаимодействовать с ними, при инициализации FactoryRegistry добавьте DeviceGroup в список entityTypes вместе с типажами Group, GroupManagement и GroupMembership.

Вы можете взаимодействовать с устройствами в группе, перебирая их напрямую (см. раздел Взаимодействие с устройствами в группе) или нацеливаясь на часть типа устройства в DeviceGroup (см. раздел Управление группой устройств).

Как получить список групп устройств

Чтобы отслеживать все группы устройств в структуре, вызовите home.entities(DeviceGroup) и отфильтруйте по идентификатору структуры:

home.entities(DeviceGroup).collect { allGroups ->
  val structureGroups = allGroups.filter { it.structureId == structure.id }
  for (group in structureGroups) {
    val groupName = group.trait(Group)?.Name
    println("Found group: $groupName with ID: ${group.id.id}")
  }
}

Взаимодействие с устройствами участников группы

Итерация по устройствам, входящим в группу, с помощью group.devices() позволяет точно управлять отдельными устройствами, объединять команды в один запрос, а также агрегировать или записывать атрибуты для всех устройств.

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

val members = group.devices(enableMultipartDevices = true).first()
val onOffTraits = members.mapNotNull { device ->
  device.partOrNull(OnOffLightDevice).first()?.standardTraits?.onOff
}

val responses = home.sendBatchedCommands {
  onOffTraits.map { onOff ->
    add(onOff.onBatchable())
  }
}

for (response in responses) {
  response.getOrThrow()
}

Вы также можете обновить атрибут на всех соответствующих устройствах в группе (например, задать для onTransitionTime значение 1u на всех устройствах DimmableLightDevice) и дождаться завершения в течение заданного срока:

val members = group.devices(enableMultipartDevices = true).first()

withTimeout(5.seconds) {
  members.map { device ->
    async {
      val light = device.partOrNull(DimmableLightDevice).first()
      val levelControl = light?.standardTraits?.levelControl
      if (
        levelControl?.supports(
          LevelControl.Attribute.onTransitionTime
        ) == true
      ) {
        levelControl.update { setOnTransitionTime(1u) }
      }
    }
  }.awaitAll()
}

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

val members = group.devices(enableMultipartDevices = true).first()
val minMaxMireds: UShort? = members.mapNotNull { device ->
  val light = device.partOrNull(ColorTemperatureLightDevice).first()
  light?.standardTraits?.colorControl?.colorTempPhysicalMaxMireds
}.minOrNull()

Как создать группу устройств

Чтобы создать группу устройств, используйте GroupManagement в структуре:

val groupManagement = structure.trait(GroupManagementTrait)
val response = groupManagement?.createUserDefinedGroup(
  name = "Living room lights",
  memberDeviceObjectIds = listOf(light1.id.id, light2.id.id)
)

Как управлять пользователями в группе

Чтобы добавить участников в существующую группу:

structure.trait(GroupManagementTrait)?.addGroupMembers(
  groupObjectId = group.id.id,
  memberDeviceObjectIds = listOf(light3.id.id)
)

Чтобы удалить участников из группы:

structure.trait(GroupManagementTrait)?.removeGroupMembers(
  groupObjectId = group.id.id,
  memberDeviceObjectIds = listOf(light1.id.id)
)

Как изменить название группы устройств

Чтобы переименовать группу устройств, измените атрибут name в ее объекте GroupTrait:

group.trait(GroupTrait)?.update {
  name = "Movie corner"
}

Как удалить группу устройств

Чтобы удалить группу устройств из структуры:

structure.trait(GroupManagementTrait)?.deleteGroup(
  groupObjectId = group.id.id
)

Автоматизации

Точкой входа в Automation API является структура. Подробнее об Automation API для Android…