Structure API на iOS

Доступ к Structure API можно получить через Home API для iOS.

Чтобы работать с API Structure, сначала импортируйте пакет GoogleHomeSDK в свое приложение:

import GoogleHomeSDK

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

Некоторые методы в Home API вызывают HomeError, поэтому мы рекомендуем использовать блок do-catch, чтобы перехватывать HomeError при таких вызовах.

При обработке HomeError проверьте поля code и message, чтобы узнать, что пошло не так.

Если не обработать ошибки, приложение будет работать со сбоями.

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

Structure API

Home представляет граф дома и является точкой входа в Structure API. Он содержит ссылки на дома, комнаты и устройства.

Structure представляет структуру в графе дома. Он предоставляет доступ к метаданным структуры, таким как id и name.

Чтобы получить все структуры в аккаунте, используйте structures(). Структуры возвращаются в виде объекта Query, который предлагает несколько способов использования данных:

API Описание
stream() Возвращает Publisher, который выдает каждый объект по отдельности по мере внесения изменений.
batched() Возвращает Publisher, который выдает текущий результат в виде Set объектов. Каждый созданный объект Set представляет текущее состояние графа объектов.
list() Возвращает текущий результат в виде Set объектов.

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

@Published public private(set) var structures: [Structure] = []
private var structuresCancellable: AnyCancellable?

  self.structuresCancellable = home
    .structures()
    .batched()
    .receive(on: DispatchQueue.main)
    .map { Array($0) }
    .catch {
      // Failed to load structures
      return Just([Structure]())
    }
    .assign(to: \.structures, on: self)

Примеры вызовов структуры

Получить набор структур

Вызов list() для Query<Structure> возвращает последний набор элементов:

// Get a stream of all structures accessible to the user
let allStructuresChanges = self.home.structures()
let allStructures = try? await allStructuresChanges.list()

При разработке реактивного приложения лучше использовать вызовы batched() и stream(), а не list(), поскольку они автоматически создают данные при изменении графа дома.

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

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

// Get a stream of changes taking place on a structure.
let structureChanges = try await home.structures().list().filter { $0.id == structureID }

// Get a snapshot of the structure.
let structure = structureChanges.first!

// Get structure properties
print("id \(structure.id) ")
print("name \(structure.name) ")

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

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

do {
  structure1 = try await home.structures().list().first(where: { $0.name == "Main House" })
} catch let _ as HomeError {
  // Code for handling the exception
}

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

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

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

var structure1: Structure!
var structure2: Structure!
do {
  structure1 = try await home.structures().list().first(where: { $0.name == "Main House" })
} catch let _ as HomeError {
  // Code for handling the exception
}
do {
  structure2 = try await home.structures().list().first(where: { $0.name == "Guest Cottage" })
} catch let _ as HomeError {
  // Code for handling the exception
}

Номера

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

Используйте Home.rooms(), чтобы получить список всех комнат в аккаунте, а затем roomID = device.roomID, чтобы показать устройства в каждой комнате.

self.home.rooms().batched()
  .combineLatest(self.home.devices().batched())
  .receive(on: DispatchQueue.main)
  .catch { error in
    // Failed to load rooms and devices
    return Just((Set<Room>(), Set<HomeDevice>()))
  }
  .map { rooms, devices in
    var devicesByRoom = [Room: [HomeDevice]]()
    for room in rooms where room.structureID == currentStructureID {
      devicesByRoom[room] = devices.filter { $0.roomID == room.id }
    }
    return devicesByRoom
  }.assign(to: &self.$devicesByRoom)

Примеры звонков в переговорные комнаты

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

С помощью класса Home можно получить список комнат и доступ к их свойствам:

let allRoomsChanges = self.home.rooms()
let allRooms = try await allRoomsChanges.list()
let room = allRooms.first!
XCTAssertTrue(allRooms.contains(room))

print("id \(room.id) ")
print("name \(room.name) ")

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

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

let testName = "Test Room Name"
var newRoom: Room!
do {
  newRoom = try await structure.createRoom(name: testName)
  XCTAssertNotNil(newRoom)
} catch let _ as HomeError {
  // Code for handling the exception
}

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

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

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

Вы также можете удалить комнату, используя ее идентификатор:

let roomToDelete = allRooms.first(where: { $0.id == room.id })
if let roomToDelete1 = roomToDelete {
  do {
    try await structure.deleteRoom(roomToDelete1)
  } catch let _ as HomeError {
    // Code for handling the exception
  }
}

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

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

Structure также позволяет переместить устройство в другую комнату:

do {
  try await structure.move(device: light, to: room)
} catch let _ as HomeError {
  // Code for handling the exception
}

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

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

let updatedRoom = try await theRoom.setName("new room name")

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

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

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

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

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

Группы устройств представлены объектом DeviceGroup, который отличается от HomeDevice. Чтобы взаимодействовать с группами устройств, при настройке ReferencedAutomationTypes в Home.configure укажите нужные типы и характеристики устройств, а также характеристики GroupTrait, GroupManagementTrait и GroupMembershipTrait.

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

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

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

let groups = try await home.entities(DeviceGroup.self).list()
for group in groups {
  let groupTrait = await group.traits.get(Google.GroupTrait.self)
  let name = groupTrait?.attributes.name ?? "Device Group"
  print("Found group: \(name) with ID: \(group.id)")
}

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

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

Сначала отфильтруйте устройства в доме, проверив GroupMembershipTrait каждого устройства на наличие идентификатора целевой группы:

let allDevices = try await home.devices().list()
var members: [HomeDevice] = []
for device in allDevices {
  if let membership = await device.traits.get(
    Google.GroupMembershipTrait.self
  ),
  membership.attributes.groupObjectIdsArray.contains(group.id) {
    members.append(device)
  }
}

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

var onOffTraits: [Matter.OnOffTrait] = []
for device in members {
  if let light = await device.types.get(OnOffLightDeviceType.self),
     let onOff = light.matterTraits.onOffTrait {
    onOffTraits.append(onOff)
  }
}

let traitsToUse = onOffTraits
let responses = try await home.sendBatchedCommands { batcher in
  return try traitsToUse.map { batcher.add(try $0.onBatchable()) }
}

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

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

try await withThrowingTaskGroup(of: Void.self) { taskGroup in
  taskGroup.addTask {
    try await Task.sleep(for: .seconds(5))
    throw CancellationError()
  }

  for device in members {
    taskGroup.addTask {
      guard let light = await device.types.get(DimmableLightDeviceType.self),
            let levelControl = light.matterTraits.levelControlTrait,
            levelControl.attributes.$onTransitionTime.isSupported
      else { return }
      _ = try await levelControl.update {
        $0.setOnTransitionTime(1)
      }
    }
  }

  for _ in 0..<members.count {
    try await taskGroup.next()
  }
  taskGroup.cancelAll()
}

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

var maxMiredsValues: [UInt16] = []
for device in members {
  if let light = await device.types.get(ColorTemperatureLightDeviceType.self),
     let mireds = light.matterTraits.colorControlTrait?.attributes
       .colorTempPhysicalMaxMireds {
    maxMiredsValues.append(mireds)
  }
}
let minMaxMireds = maxMiredsValues.min()

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

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

let groupsManagementTrait =
  try await structure.traits(Google.GroupManagementTrait)
let _ = try await groupsManagementTrait.createUserDefinedGroup(
  withName: "Kitchen Lights",
  withDevices: [light1, light2]
)

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

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

let groupManagementTrait =
  try await structure.traits(Google.GroupManagementTrait.self)
groupManagementTrait.addGroupMembers(
  groupObjectId: group.id,
  memberDeviceObjectIds = [light3.id],
)

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

let groupManagementTrait =
  try await structure.traits(Google.GroupManagementTrait.self)
_ = try await groupManagementTrait.removeGroupMembers(
  groupObjectId: group.id,
  memberDeviceObjectIdsArray: Array(removedIDs)
)

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

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

let groupTrait = group.traits.get(Google.GroupTrait.self)
_ = try await groupTrait.update { $0.name = "Movie Corner" }

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

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

let groupManagementTrait =
  try await structure.traits.get(Google.GroupManagementTrait.self)

_ = try await groupManagementTrait.deleteGroup(groupObjectId: group.id)