API Structure sur Android

Les API Structure sont accessibles via les API Home pour Android. Importez ces packages dans votre application :

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

Gestion des exceptions

Toutes les méthodes des API Home peuvent générer une exception HomeException. Nous vous recommandons donc d'utiliser un bloc try-catch pour intercepter HomeException sur tous les appels.

Lorsque vous gérez HomeException, vérifiez ses champs error.code et error.message pour savoir ce qui s'est mal passé. Il peut également y avoir des codes de sous-erreur. Appelez donc la méthode getSubErrorCodes() et vérifiez le résultat.

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

Pour en savoir plus, consultez Gestion des erreurs.

Exemples d'appels

Obtenir la liste des structures

Une fois initialisé, un appel structures() renvoie un flux de structures auquel vous pouvez accéder :

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

L'API structures() est un flux qui peut ne pas renvoyer immédiatement une liste valide de structures. Si votre application est réactive et s'abonne à ce flux pour 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.

Si la programmation impérative est fortement requise au lieu de la programmation réactive, un opérateur de flux terminal peut être utilisé :

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

Cet appel attend qu'une liste valide de structures soit transmise via le flux et expire si la liste n'est pas reçue dans un délai d'expiration désigné par l'application.

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

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 :

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

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

Obtenir la liste des salons

Une fois que vous disposez d'une structure, vous pouvez obtenir la liste des pièces et accéder à leurs propriétés :

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

Créer un salon

Pour créer un salon :

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

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 en utilisant uniquement un ID :

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

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

Une fois que vous avez une structure, vous pouvez déplacer des appareils vers une autre pièce de cette structure :

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

Si vous ne disposez que des ID d'appareil et de pièce, vous pouvez également déplacer des appareils :

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

Modifier le nom d'une pièce

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

livingRoom.setName("Living Room")

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.

Dans l'écosystème Google Home, pour la plupart des types d'appareils, les utilisateurs peuvent accorder des autorisations pour tous les appareils de ce type en même temps. Pour les types d'appareils sensibles ou restreints, tels que les serrures, les caméras ou les sonnettes, les utilisateurs doivent accorder l'autorisation individuellement.

Pour déterminer si un utilisateur a accordé l'autorisation d'accéder à un type d'appareil sensible ou restreint, utilisez la fonction consentedDeviceTypes() au niveau de la structure :

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

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 interroger des groupes d'appareils et interagir avec eux, incluez DeviceGroup dans la liste entityTypes lors de l'initialisation de votre FactoryRegistry, ainsi que les caractéristiques Group, GroupManagement et GroupMembership.

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 observer tous les groupes d'appareils d'une structure, appelez home.entities(DeviceGroup) et filtrez par l'ID de la structure :

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

Interagir avec les appareils des membres du groupe

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

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

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

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

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

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 :

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

Créer un groupe d'appareils

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

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

Gérer les groupes

Pour ajouter des membres à un groupe existant :

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

Pour supprimer des membres d'un groupe :

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

Modifier le nom d'un groupe d'appareils

Pour renommer un groupe d'appareils, modifiez l'attribut de nom sur le GroupTrait du groupe :

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

Supprimer un groupe d'appareils

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

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

Automatisations

Le point d'entrée de l'API Automation est une structure. Pour en savoir plus sur les automatisations dans les API Home, consultez Présentation de l'API Automation sur Android.