Interfejsy Structure API na iOS

Dostęp do interfejsów Structure API można uzyskać za pomocą interfejsów API Google Home na iOS.

Aby korzystać z interfejsów Structure API, najpierw zaimportuj do aplikacji pakiet GoogleHomeSDK:

import GoogleHomeSDK

Obsługa błędów

Niektóre metody w interfejsach API Google Home wywołują błąd HomeError, dlatego zalecamy użycie bloku do-catch, aby przechwycić błąd HomeError w tych wywołaniach.

Podczas obsługi błędu HomeError sprawdź pola code i message, aby dowiedzieć się, co poszło nie tak.

Wszelkie nieobsługiwane błędy spowodują awarię aplikacji.

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

Structure API

Home reprezentuje Home Graph i jest punktem wejścia do interfejsu Structure API. Zawiera odniesienia do domów, pomieszczeń i urządzeń.

Structure reprezentuje strukturę na wykresie domu. Zapewnia dostęp do metadanych struktury, takich jak id i name.

Użyj metody structures(), aby pobrać wszystkie domy na koncie. Struktury są zwracane w postaci obiektu Query, który oferuje różne sposoby wykorzystania danych:

Interfejs API Opis
stream() Zwraca obiekt Publisher, który emituje każdy obiekt osobno w miarę wprowadzania zmian.
batched() Zwraca Publisher, który emituje bieżący wynik jako Set obiektów. Każdy wyemitowany obiekt Set reprezentuje bieżący stan grafu obiektów.
list() Zwraca bieżący wynik jako Set obiektów.

Wywołanie structures().list() może nie zwrócić od razu prawidłowego zestawu struktur. Jeśli aplikacja jest reaktywna i wywołuje stream(), aby subskrybować wszystkie zmiany struktury w celu sterowania interfejsem, powinna zostać zwrócona prawidłowa lista struktur. Istnieją inne sytuacje, w których może zostać zwrócona pusta lista domów, np. gdy telefon użytkownika straci połączenie lub gdy użytkownik cofnie uprawnienia aplikacji. Pamiętaj, aby uwzględnić te przypadki w aplikacji.

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

Przykładowe wywołania struktury

Pobieranie zestawu struktur

Wywołanie metody list() na obiekcie Query<Structure> zwraca najnowszy zbiór elementów:

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

Podczas projektowania aplikacji reaktywnej warto używać wywołań batched() i stream() zamiast list(), ponieważ automatycznie generują one dane, gdy zmienia się wykres domu.

Pobieranie właściwości struktury

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

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

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:

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

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

Pokoje

Pomieszczenie zawiera grupę urządzeń. Pomieszczenie jest zawsze częścią domu, a dom może mieć wiele pomieszczeń. Usunięcie pomieszczenia z domu nie powoduje usunięcia z niego urządzeń. Jeśli jednak pomieszczenie zostanie usunięte, urządzenia w nim staną się nieprzypisane.

Użyj Home.rooms(), aby pobrać wszystkie pomieszczenia na koncie, a następnie użyj roomID = device.roomID, aby wyświetlić odpowiednie urządzenia w każdym pomieszczeniu.

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)

Rozmowy w pokoju próbnym

Pobieranie listy pokoi

Za pomocą klasy Home możesz uzyskać listę sal i uzyskać dostęp do ich właściwości:

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

Utwórz pokój

Aby utworzyć nowy pokój w 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
}

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, używając jego identyfikatora:

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

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

Structure umożliwia też przeniesienie urządzenia do innego pomieszczenia:

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

Zmienianie nazwy pomieszczenia

Aby zmienić nazwę pokoju, wywołaj metodę setName(_:):

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

Podczas zmiany nazwy pokoju pierwotna struktura Room pozostaje bez zmian, a zmiana jest odzwierciedlana w zwróconym zaktualizowanym obiekcie 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.

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 umożliwić interakcję z grupami urządzeń, podczas konfigurowania ReferencedAutomationTypes w Home.configure uwzględnij odpowiednie typy i cechy urządzeń oraz cechy GroupTrait, GroupManagementTrait i GroupMembershipTrait.

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 wysłać zapytanie do wszystkich grup urządzeń w domu:

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

Interakcja z urządzeniami członków grupy

Iteracja po urządzeniach członków grupy 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.

Najpierw przefiltruj urządzenia w domu, sprawdzając, czy każde z nich ma w GroupMembershipTrait identyfikator grupy docelowej:

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

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

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

Możesz też jednocześnie zaktualizować atrybut na wszystkich pasujących urządzeniach należących do grupy (np. ustawić onTransitionTime na 1 na wszystkich urządzeniach DimmableLightDeviceType) i poczekać na zakończenie w określonym terminie:

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

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

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

Tworzenie grupy urządzeń

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

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

Zarządzanie członkostwem w grupach

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

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

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

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

Zmienianie nazwy grupy urządzeń

Aby zmienić nazwę grupy urządzeń, zaktualizuj pole name w obiekcie GroupTrait:

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

Usuwanie grupy urządzeń

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

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

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