Interfejsy Structure API na Androidzie

Dostęp do interfejsów Structure API można uzyskać za pomocą interfejsów API Google Home na Androida. Zaimportuj te pakiety do aplikacji:

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

Obsługa błędów

Dowolna metoda w interfejsach Home API może wywołać błąd HomeException, dlatego zalecamy używanie bloku try-catch do przechwytywania błędu HomeException we wszystkich wywołaniach.

Podczas obsługi HomeException sprawdź pola error.code i  error.message, aby dowiedzieć się, co poszło nie tak. Mogą też występować kody błędów podrzędnych, więc wywołaj metodę getSubErrorCodes() i sprawdź wynik.

Wszelkie nieobsługiwane wyjątki spowodują awarię aplikacji.

Więcej informacji znajdziesz w artykule na temat obsługi błędów.

Przykładowe rozmowy

Pobieranie listy domów

Po zainicjowaniu wywołanie structures() zwraca przepływ struktur, do których masz dostęp:

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

Interfejs API structures() to proces, który może nie zwrócić od razu prawidłowej listy struktur. Jeśli aplikacja jest reaktywna i subskrybuje ten przepływ, aby sterować interfejsem, powinna ostatecznie zwrócić prawidłową listę struktur. Istnieją inne sytuacje, w których może zostać zwrócona pusta lista struktur, na przykład gdy telefon użytkownika straci połączenie lub gdy użytkownik cofnie uprawnienia aplikacji. Pamiętaj, aby uwzględnić te przypadki w aplikacji.

Jeśli zamiast programowania reaktywnego wymagane jest programowanie imperatywne, można użyć operatora terminal flow:

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

To wywołanie czeka na prawidłową listę struktur, która ma zostać przesłana przez przepływ, i kończy się, jeśli lista nie zostanie odebrana w określonym przez aplikację czasie oczekiwania.

Pobieranie właściwości struktury

Mając listę struktur, możesz uzyskać dostęp do ich właściwości:

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

Znajdowanie domu według nazwy

Jeśli znasz nazwę struktury, możesz też uzyskać do niej dostęp za pomocą właściwości name:

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

Z tego miejsca można uzyskać dostęp do domów, pomieszczeń i urządzeń w każdym domu.

Praca z wieloma domami

Aby używać więcej niż jednej struktury, uzyskaj osobne odwołanie do każdej z nich:

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
}

Pobieranie listy pokoi

Mając strukturę, możesz uzyskać listę pomieszczeń i dostęp do ich właściwości:

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

Utwórz pokój

Aby utworzyć nowy pokój:

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

Usuwanie pomieszczenia

Możesz też usunąć pomieszczenie:

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

Możesz też usunąć pokój, podając tylko identyfikator:

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

Jeśli pomieszczenie z urządzeniami zostanie usunięte, urządzenia nadal będą w strukturze, ale nie będą już przypisane do pomieszczenia.

Przenoszenie urządzeń do innego pomieszczenia

Gdy masz już strukturę, możesz przenosić urządzenia do innego pomieszczenia w jej obrębie:

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

Jeśli masz tylko identyfikatory urządzeń i pomieszczeń, możesz też przenieść urządzenia:

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

Zmienianie nazwy pomieszczenia

Wywołaj metodę setName(), aby zmienić nazwę pokoju:

livingRoom.setName("Living Room")

Nazwy zostaną obcięte, jeśli przekroczą limit 60 punktów kodowych Unicode (znaków). Nie zostaną wyświetlone żadne błędy. Deweloperzy są odpowiedzialni za obsługę długich nazw i mogą na przykład zdecydować, czy chcą informować użytkowników, że nazwy będą skracane.

W ekosystemie Google Home w przypadku większości typów urządzeń użytkownicy mogą przyznać uprawnienia wszystkim urządzeniom danego typu jednocześnie. W przypadku urządzeń poufnych lub z ograniczonym dostępem, takich jak zamki, kamery czy dzwonki, użytkownicy muszą przyznać uprawnienia do każdego z nich osobno.

Aby sprawdzić, czy użytkownik przyznał uprawnienia dostępu do urządzenia typu poufnego lub zastrzeżonego, użyj funkcji consentedDeviceTypes() na poziomie struktury:

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

Grupy urządzeń

Grupa urządzeń to zdefiniowany przez użytkownika zbiór urządzeń w ramach jednej struktury. Członkostwo w grupie jest zachowywane w usługach Google i jest widoczne dla innych członków struktury oraz innych aplikacji mających dostęp do struktury.

Podobnie jak fizyczne pomieszczenia, grupy urządzeń należą do jednego domu. W przeciwieństwie do pomieszczeń grupy urządzeń są jednak grupami logicznymi. Urządzenie może należeć do wielu grup urządzeń jednocześnie. Grupy urządzeń mogą zawierać maksymalnie 100 urządzeń.

Grupy urządzeń są reprezentowane przez element DeviceGroup, który różni się od elementu HomeDevice. Aby wysyłać zapytania do grup urządzeń i wchodzić z nimi w interakcje, podczas inicjowania FactoryRegistry uwzględnij DeviceGroup na liście entityTypes wraz z atrybutami Group, GroupManagement i GroupMembership.

Możesz wchodzić w interakcje z urządzeniami w grupie, bezpośrednio przechodząc przez urządzenia członkowskie (patrz Interakcja z urządzeniami członkowskimi grupy) lub kierując na część typu urządzenia na DeviceGroup (patrz Sterowanie grupą urządzeń).

Pobieranie listy grup urządzeń

Aby obserwować wszystkie grupy urządzeń w strukturze, wywołaj home.entities(DeviceGroup) i przefiltruj według identyfikatora struktury:

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

Interakcja z urządzeniami członków grupy

Iteracja po urządzeniach członków grupy za pomocą group.devices() zapewnia szczegółową kontrolę nad poszczególnymi członkami, umożliwia grupowanie poleceń w jednym żądaniu oraz agregowanie i zapisywanie atrybutów członków.

Aby na przykład wysłać pojedyncze żądanie zbiorcze z poleceniami dotyczącymi cechy do wszystkich zgodnych członków grupy:

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

Możesz też zaktualizować atrybut na wszystkich pasujących urządzeniach członkowskich w grupie (np. ustawić onTransitionTime na 1u na wszystkich urządzeniach członkowskich DimmableLightDevice) i poczekać na zakończenie w określonym terminie:

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

Aby zagregować atrybut dla członków grupy, np. znaleźć minimalną wartość colorTempPhysicalMaxMireds dla wszystkich świateł o temperaturze kolorów w grupie:

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

Tworzenie grupy urządzeń

Aby utworzyć grupę urządzeń, użyj GroupManagement w domu:

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

Zarządzanie członkostwem w grupach

Aby dodać członków do istniejącej grupy:

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

Aby usunąć członków z grupy:

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

Zmienianie nazwy grupy urządzeń

Aby zmienić nazwę grupy urządzeń, zaktualizuj atrybut nazwy w GroupTrait grupy:

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

Usuwanie grupy urządzeń

Aby usunąć grupę urządzeń z domu:

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

Automatyzacja

Punktem wejścia do interfejsu Automation API jest dom. Więcej informacji o automatyzacjach w interfejsach API Google Home znajdziesz w artykule Omówienie interfejsu Automation API na Androidzie.