API Structure su Android

È possibile accedere alle API Structure tramite le API Home per Android. Importa questi pacchetti nella tua app:

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

Gestione degli errori

Qualsiasi metodo nelle API Home può generare un'eccezione HomeException, pertanto ti consigliamo di utilizzare un blocco try-catch per rilevare HomeException su tutte le chiamate.

Quando gestisci HomeException, controlla i campi error.code e error.message per scoprire cosa non ha funzionato. Potrebbero esserci anche codici di errore secondari, quindi chiama il metodo getSubErrorCodes() e controlla il risultato.

Eventuali eccezioni non gestite causeranno l'arresto anomalo dell'app.

Per saperne di più, consulta Gestione degli errori.

Chiamate di esempio

Recupera un elenco di strutture

Una volta inizializzata, una chiamata structures() restituisce un flusso di strutture accessibili:

// 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() è un flusso che potrebbe non restituire immediatamente un elenco valido di strutture. Se la tua app è reattiva e si iscrive a quel flusso per gestire la UI, alla fine dovrebbe essere restituito un elenco valido di strutture. Esistono altre situazioni in cui potrebbe essere restituito un elenco di strutture vuoto, ad esempio se lo smartphone dell'utente perde la connettività o se l'utente ha revocato le autorizzazioni alla tua app. Assicurati di gestire questi casi nella tua app.

In alternativa, se la programmazione imperativa è fortemente richiesta al posto della programmazione reattiva, è possibile utilizzare un operatore di flusso terminale:

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

Questa chiamata attende che un elenco valido di strutture passi attraverso il flusso e scade se l'elenco non viene ricevuto entro un timeout designato dall'app.

Recupera le proprietà della struttura

Con l'elenco delle strutture a portata di mano, puoi accedere alle relative proprietà:

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

Trova una struttura per nome

Se conosci il nome di una struttura, puoi accedervi anche utilizzando la proprietà name:

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

Da qui è possibile accedere alle proprietà, alle stanze e ai dispositivi di ogni struttura.

Utilizza più strutture

Per utilizzare più di una struttura, ottieni un riferimento separato per ogni struttura:

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
}

Visualizza un elenco di stanze

Una volta ottenuta una struttura, puoi recuperare un elenco di stanze e accedere alle relative proprietà:

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

Crea una stanza virtuale

Per creare una nuova stanza:

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

Eliminare una stanza

In alternativa, puoi eliminare una stanza:

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

Puoi anche eliminare una stanza con un solo ID:

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

Se una stanza con dispositivi viene eliminata, i dispositivi rimarranno nella struttura, ma non saranno più assegnati a una stanza.

Sposta i dispositivi in un'altra stanza

Una volta creata una struttura, puoi spostare i dispositivi in un'altra stanza all'interno di quella struttura:

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

Se hai solo gli ID dispositivo e stanza, puoi anche spostare i dispositivi:

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

Modifica il nome di una stanza

Chiama il metodo setName() per modificare il nome di una stanza:

livingRoom.setName("Living Room")

I nomi verranno troncati se superano il limite di 60 punti di codice Unicode (caratteri) e non verranno generati errori. Gli sviluppatori sono responsabili della gestione dei nomi lunghi e, ad esempio, possono decidere se informare gli utenti che i nomi verranno troncati.

Nell'ecosistema Google Home, per la maggior parte dei tipi di dispositivi, gli utenti possono concedere le autorizzazioni per tutti i dispositivi di quel tipo contemporaneamente. Per i tipi di dispositivi sensibili o con limitazioni, come serrature, videocamere o campanelli, gli utenti devono concedere l'autorizzazione singolarmente.

Per determinare se un utente ha concesso l'autorizzazione ad accedere a un tipo di dispositivo sensibile o con limitazioni, utilizza la funzione consentedDeviceTypes() a livello di struttura:

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

Gruppi di dispositivi

Un gruppo di dispositivi rappresenta una raccolta di dispositivi definita dall'utente all'interno di una singola struttura. L'iscrizione al gruppo viene conservata nei servizi Google ed è visibile agli altri membri della struttura e alle altre app con accesso alla struttura.

Come le stanze fisiche, i gruppi di dispositivi appartengono a una singola struttura. A differenza delle stanze, i gruppi di dispositivi sono raggruppamenti logici. Un dispositivo può appartenere a più gruppi di dispositivi contemporaneamente. I gruppi di dispositivi possono contenere fino a 100 dispositivi.

I gruppi di dispositivi sono rappresentati dall'entità DeviceGroup, che è distinta da HomeDevice. Per eseguire query e interagire con i gruppi di dispositivi, includi DeviceGroup nell'elenco entityTypes durante l'inizializzazione di FactoryRegistry, insieme ai tratti Group, GroupManagement e GroupMembership.

Puoi interagire con i dispositivi di un gruppo eseguendo l'iterazione direttamente sui dispositivi membri (consulta Interagisci con i dispositivi membri del gruppo) o scegliendo come target una parte del tipo di dispositivo su DeviceGroup (consulta Controlla un gruppo di dispositivi).

Visualizza un elenco dei gruppi di dispositivi

Per osservare tutti i gruppi di dispositivi in una struttura, chiama home.entities(DeviceGroup) e filtra in base all'ID della struttura:

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

Interagisci con i dispositivi dei membri del gruppo

L'iterazione sui dispositivi membri di un gruppo con group.devices() ti offre un controllo granulare sui singoli membri, ti consente di raggruppare i comandi in un'unica richiesta e di aggregare o scrivere attributi tra i membri.

Ad esempio, per inviare una singola richiesta batch con i comandi per un tratto a tutti i membri compatibili di un gruppo:

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

Puoi anche aggiornare un attributo su tutti i dispositivi membri corrispondenti di un gruppo (ad esempio, impostando onTransitionTime su 1u su tutti i membri DimmableLightDevice) e attendere il completamento entro una scadenza:

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

Per aggregare un attributo tra i membri di un gruppo, ad esempio per trovare il valore minimo di colorTempPhysicalMaxMireds per tutte le luci con temperatura di colore nel gruppo:

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

Crea un gruppo di dispositivi

Utilizza GroupManagement sulla struttura per creare un gruppo di dispositivi:

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

Gestione dell'appartenenza ai gruppi

Per aggiungere membri a un gruppo esistente:

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

Per rimuovere membri da un gruppo:

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

Modifica il nome di un gruppo di dispositivi

Per rinominare un gruppo di dispositivi, aggiorna l'attributo name nel GroupTrait del gruppo:

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

Elimina un gruppo di dispositivi

Per eliminare un gruppo di dispositivi da una struttura:

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

Automazioni

Il punto di accesso all'API Automation è tramite una struttura. Per scoprire di più sulle automazioni nelle API Home, consulta la panoramica dell'API Automation su Android.