API Structure sur iOS

Les API Structure sont accessibles via les API Home pour iOS.

Pour utiliser les API Structure, commencez par importer le package GoogleHomeSDK dans votre application :

import GoogleHomeSDK

Gestion des exceptions

Certaines méthodes des API Home génèrent une HomeError. Nous vous recommandons donc d'utiliser un bloc do-catch pour intercepter HomeError lors de ces appels.

Lorsque vous gérez HomeError, vérifiez ses champs code et message pour savoir ce qui s'est mal passé.

Toute erreur non gérée entraînera le plantage de votre application.

Pour en savoir plus, consultez Gestion des erreurs.

API Structure

Home représente le Home Graph et constitue le point d'entrée de l'API Structure. Il fournit des références aux structures, aux pièces et aux appareils.

Structure représente une structure dans votre Home Graph. Il permet d'accéder aux métadonnées de la structure, telles que id et name.

Utilisez structures() pour obtenir toutes les structures de votre compte. Les structures sont renvoyées sous la forme d'un Query, qui offre plusieurs façons d'utiliser ses données :

API Description
stream() Renvoie un Publisher qui émet chaque objet individuellement à mesure que des modifications sont apportées.
batched() Renvoie un Publisher qui émet le résultat actuel sous la forme d'un Set d'objets. Chaque Set émis représente l'état actuel du graphique d'objets.
list() Renvoie le résultat actuel sous la forme d'un Set d'objets.

L'appel structures().list() peut ne pas renvoyer immédiatement un ensemble valide de structures. Si votre application est réactive et appelle stream() pour s'abonner à toutes les modifications de structure afin de piloter l'UI, une liste valide de structures devrait être renvoyée. Une liste de structures vide peut être renvoyée dans d'autres situations, par exemple si le téléphone de l'utilisateur perd la connexion ou si l'utilisateur a révoqué les autorisations accordées à votre application. Veillez à gérer ces cas dans votre application.

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

Exemples d'appels de structure

Obtenir un ensemble de structures

L'appel de list() sur un Query<Structure> renvoie l'ensemble d'éléments le plus récent :

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

Lorsque vous concevez une application réactive, vous devez utiliser les appels batched() et stream() plutôt que list(), car ils produisent automatiquement des données lorsque le graphique de la maison change.

Obtenir les propriétés de la structure

Une fois que vous disposez de la liste des structures, vous pouvez accéder à leurs propriétés :

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

Rechercher une structure par son nom

Si vous connaissez le nom d'une structure, vous pouvez également y accéder à l'aide de la propriété name :

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

Vous pouvez alors accéder aux propriétés, aux pièces et aux appareils de chaque maison.

Utiliser plusieurs structures

Pour utiliser plusieurs structures, obtenez une référence distincte pour chacune d'elles :

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
}

Chambres

Une pièce contient un groupe d'appareils. Une pièce fait toujours partie d'une structure, et une structure peut comporter plusieurs pièces. La suppression d'une pièce d'une structure ne supprime pas les appareils de cette pièce de la structure. Toutefois, si la pièce est supprimée, les appareils qu'elle contient ne sont plus attribués.

Utilisez Home.rooms() pour récupérer toutes les pièces du compte, puis roomID = device.roomID pour afficher les appareils correspondants dans chaque pièce.

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)

Exemples d'appels Room

Obtenir la liste des salons

La classe Home vous permet d'obtenir la liste des pièces et d'accéder à leurs propriétés :

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

Créer un salon

Pour créer un salon dans un 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
}

Supprimer une pièce

Vous pouvez également supprimer une salle :

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

Vous pouvez également supprimer une salle à l'aide de son ID :

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

Si une pièce contenant des appareils est supprimée, les appareils restent dans la structure, mais ne sont plus attribués à une pièce.

Déplacer des appareils dans une autre pièce

Structure vous permet également de déplacer un appareil dans une autre pièce :

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

Modifier le nom d'une pièce

Appelez la méthode setName(_:) pour modifier le nom d'une pièce :

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

Lorsque vous modifiez le nom d'une pièce, la structure Room d'origine reste inchangée, et la modification est reflétée dans l'objet Room mis à jour renvoyé.

Les noms seront tronqués s'ils dépassent la limite de 60 points de code Unicode (caractères), et aucune erreur ne sera générée. Les développeurs sont responsables de la gestion des noms longs et peuvent, par exemple, décider d'informer les utilisateurs que les noms seront tronqués.

Groupes d'appareils

Un groupe d'appareils représente un ensemble d'appareils défini par l'utilisateur au sein d'une même structure. L'appartenance au groupe est conservée dans les services Google et est visible par les autres membres de la structure et les autres applications ayant accès à la structure.

Comme les pièces physiques, les groupes d'appareils appartiennent à une seule structure. Mais contrairement aux pièces, les groupes d'appareils sont des regroupements logiques. Un appareil peut appartenir à plusieurs groupes d'appareils simultanément. Les groupes d'appareils peuvent contenir jusqu'à 100 appareils.

Les groupes d'appareils sont représentés par l'entité DeviceGroup, qui est distincte de HomeDevice. Pour interagir avec les groupes d'appareils, incluez les types d'appareils et les caractéristiques pertinents lors de la configuration de ReferencedAutomationTypes dans Home.configure, ainsi que les caractéristiques GroupTrait, GroupManagementTrait et GroupMembershipTrait.

Vous pouvez interagir avec les appareils d'un groupe soit en effectuant une itération directement sur ses appareils membres (voir Interagir avec les appareils membres d'un groupe), soit en ciblant une partie de type d'appareil sur le DeviceGroup (voir Contrôler un groupe d'appareils).

Obtenir la liste des groupes d'appareils

Pour interroger tous les groupes d'appareils de la maison :

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 avec les appareils des membres du groupe

L'itération sur les appareils membres d'un groupe vous permet de contrôler précisément les membres individuels, de regrouper des commandes dans une seule requête, et d'agréger ou d'écrire des attributs sur les membres.

Tout d'abord, filtrez les appareils de la maison en vérifiant l'GroupMembershipTrait de chaque appareil pour l'ID du groupe cible :

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

Pour envoyer une seule requête par lot avec des commandes pour un trait sur tous les membres compatibles d'un groupe :

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

Vous pouvez également mettre à jour un attribut sur tous les appareils membres correspondants d'un groupe (par exemple, définir onTransitionTime sur 1 sur tous les membres DimmableLightDeviceType) simultanément et attendre la fin de l'opération dans un délai imparti :

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

Pour agréger un attribut entre les membres d'un groupe, par exemple pour trouver la valeur minimale de colorTempPhysicalMaxMireds pour toutes les lumières à température de couleur du groupe :

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

Créer un groupe d'appareils

Utilisez GroupManagementTrait sur la structure pour créer un groupe d'appareils :

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

Gérer les groupes

Pour ajouter des membres à un groupe existant :

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

Pour supprimer des membres d'un groupe :

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

Modifier le nom d'un groupe d'appareils

Pour renommer un groupe d'appareils, mettez à jour le champ name de GroupTrait :

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

Supprimer un groupe d'appareils

Pour supprimer un groupe d'appareils d'une structure :

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

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