Estruturar APIs no iOS

As APIs de estrutura podem ser acessadas pelas APIs do Google Home para iOS.

Para trabalhar com as APIs Structure, primeiro importe o pacote GoogleHomeSDK para seu app:

import GoogleHomeSDK

Tratamento de erros

Alguns métodos nas APIs do Google Home geram uma HomeError. Por isso, recomendamos que você use um bloco do-catch para capturar HomeError nessas chamadas.

Ao processar HomeError, verifique os campos code e message para saber o que deu errado.

Qualquer erro não processado vai causar uma falha no app.

Para mais informações, consulte Tratamento de erros.

API Structure

Home representa o Home Graph e é o ponto de entrada para a API Structure. Ele fornece referências a estruturas, ambientes e dispositivos.

Structure representa uma estrutura no Home Graph. Ele fornece acesso a metadados de estrutura, como id e name.

Use structures() para ver todas as estruturas na sua conta. As estruturas são retornadas na forma de um Query, que oferece várias maneiras de consumir os dados:

API Descrição
stream() Retorna um Publisher que emite cada objeto individualmente conforme as mudanças ocorrem.
batched() Retorna um Publisher que emite o resultado atual como um Set de objetos. Cada Set emitido representa o estado atual do gráfico de objetos.
list() Retorna o resultado atual como um Set de objetos.

A chamada structures().list() pode não retornar imediatamente um conjunto válido de estruturas. Se o app for reativo e chamar stream() para se inscrever em todas as mudanças de estrutura para controlar a interface, uma lista válida de estruturas será retornada. Há outras situações em que uma lista de estruturas vazia pode ser retornada, por exemplo, se o smartphone do usuário perder a conectividade ou se o usuário tiver revogado as permissões do app. Trate esses casos no app.

@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)

Chamadas de estrutura de amostra

Receber um conjunto de estruturas

Chamar list() em um Query<Structure> retorna o conjunto mais recente de elementos:

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

Ao projetar um app reativo, use as chamadas batched() e stream(), em vez de list(), porque elas produzem dados automaticamente quando o gráfico de casa muda.

Ver propriedades da estrutura

Com a lista de estruturas em mãos, você pode acessar as propriedades delas:

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

Encontrar uma estrutura pelo nome

Se você souber o nome de uma estrutura, também poderá acessá-la usando a propriedade name:

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

A partir daí, as propriedades, os ambientes e os dispositivos de cada estrutura ficam acessíveis.

Trabalhar com várias estruturas

Para usar mais de uma estrutura, receba uma referência separada para cada uma:

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
}

Salas

Um ambiente contém um grupo de dispositivos. Um ambiente sempre faz parte de uma estrutura, e uma estrutura pode ter vários ambientes. Remover um ambiente de uma estrutura não remove os dispositivos desse ambiente da estrutura. No entanto, se o ambiente for excluído, os dispositivos nele vão ficar sem atribuição.

Use Home.rooms() para recuperar todos os ambientes na conta e, em seguida, use roomID = device.roomID para mostrar os dispositivos correspondentes em cada ambiente.

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)

Exemplos de chamadas do Room

Receber uma lista de salas

Usando a classe Home, é possível acessar uma lista de salas e as propriedades delas:

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

Criar uma sala

Para criar um novo ambiente em um 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
}

Excluir um ambiente

Como alternativa, você pode excluir um ambiente:

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

Também é possível excluir um espaço usando o ID dele:

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
  }
}

Se um ambiente com dispositivos for excluído, os dispositivos ainda estarão na estrutura, mas não serão mais atribuídos a um ambiente.

Mover dispositivos para outro ambiente

O Structure também permite mover um dispositivo para outro ambiente:

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

Mudar o nome de um ambiente

Chame o método setName(_:) para mudar o nome de um ambiente:

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

Ao mudar o nome de uma sala, o struct Room original permanece o mesmo, e a mudança é refletida no objeto Room atualizado retornado.

Os nomes serão truncados se ultrapassarem o limite de 60 pontos de código Unicode (caracteres), e nenhum erro será gerado. Os desenvolvedores são responsáveis por lidar com nomes longos e, por exemplo, podem decidir se querem informar aos usuários que os nomes serão truncados.

Grupos de dispositivos

Um grupo de dispositivos representa uma coleção de dispositivos definida pelo usuário em uma única estrutura. A participação no grupo é mantida nos Serviços do Google e fica visível para outros membros da estrutura e outros apps com acesso a ela.

Assim como os ambientes físicos, os grupos de dispositivos pertencem a uma única estrutura. No entanto, ao contrário dos ambientes, os grupos de dispositivos são agrupamentos lógicos. Um dispositivo pode pertencer a vários grupos de dispositivos simultaneamente. Os grupos de dispositivos podem conter até 100 dispositivos.

Os grupos de dispositivos são representados pela entidade DeviceGroup, que é diferente de HomeDevice. Para interagir com grupos de dispositivos, inclua os tipos e atributos relevantes ao configurar ReferencedAutomationTypes em Home.configure, além dos atributos GroupTrait, GroupManagementTrait e GroupMembershipTrait.

É possível interagir com os dispositivos em um grupo iterando diretamente sobre os dispositivos membros (consulte Interagir com dispositivos membros do grupo) ou segmentando uma parte do tipo de dispositivo no DeviceGroup (consulte Controlar um grupo de dispositivos).

Receber uma lista de grupos de dispositivos

Para consultar todos os grupos de dispositivos na casa:

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

Interagir com dispositivos de membros do grupo

A iteração nos dispositivos de um grupo oferece controle detalhado sobre membros individuais, permite agrupar comandos em uma única solicitação e agregar ou gravar atributos entre os membros.

Primeiro, filtre os dispositivos na casa verificando o GroupMembershipTrait de cada dispositivo para o ID do grupo de destino:

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

Para enviar uma única solicitação em lote com comandos para um atributo em todos os membros compatíveis de um grupo:

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()
}

Também é possível atualizar um atributo em todos os dispositivos membros correspondentes em um grupo (por exemplo, definir onTransitionTime como 1 em todos os membros DimmableLightDeviceType) simultaneamente e aguardar a conclusão dentro de um prazo:

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()
}

Para agregar um atributo entre os membros de um grupo, como encontrar a colorTempPhysicalMaxMireds mínima em todas as luzes de temperatura da cor no grupo:

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()

Criar um grupo de dispositivos

Use GroupManagementTrait na estrutura para criar um grupo de dispositivos:

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

Gerenciar associação a grupos

Para adicionar participantes a um grupo:

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

Para remover participantes de um grupo:

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

Mudar o nome de um grupo de dispositivos

Para renomear um grupo de dispositivos, atualize o campo name do GroupTrait:

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

Excluir um grupo de dispositivos

Para excluir um grupo de dispositivos de uma estrutura:

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

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