<br />

> [!NOTE]
> **Note:** An application can only be granted permission to one structure at any given time.

<br />

Structure APIs may be accessed through the Home APIs for iOS.

To work with the Structure APIs, first import the `GoogleHomeSDK` package into
your app:

```swift
import GoogleHomeSDK
```

## Error handling

<br />

Some methods in the Home APIs throw a
`HomeError`, so we recommend that you use a `do-catch` block to catch
`HomeError` on those calls.

When handling `HomeError`, check its `code` and `message`
fields to learn what went wrong.

Any unhandled errors will result in your app crashing.

For more information, see
[Error handling](https://developers.home.google.com/apis/eap/ios/error-handling).

<br />

## Structure API

`Home` represents the Home graph, and is the entry point to the Structure API.
It provides references to structures, rooms, and devices.

`Structure` represents a structure in your Home graph. It provides access to
structure metadata such as `id` and `name`.

Use [`structures()`](https://developers.home.google.com/reference/swift/GoogleHomeSDK/Classes/Home.md.txt#structures) to
get all structures in your account. The structures are returned in the form of a
[`Query`](https://developers.home.google.com/reference/swift/GoogleHomeSDK/Protocols/Query.md.txt), which offers a choice
of ways of consuming its data:

| API | Description |
|---|---|
| `stream()` | Returns a `Publisher` that emits each object individually as changes take place. |
| `batched()` | Returns a `Publisher` that emits the current result as a `Set` of objects. Each emitted `Set` represents the current state of the object graph. |
| `list()` | Returns the current result as a `Set` of objects. |

The `structures().list()` call may not immediately return a valid set of
structures. If your app is reactive and calls `stream()` to subscribe to
all structure changes to drive the UI, a valid list of structures should
eventually be returned. There are other situations where an empty structure list
could be returned, for example if the user's phone loses connectivity or if the
user has revoked permissions to your app. You should be sure to handle these
cases in your app.

> [!IMPORTANT]
> **Important:** The Structure API returns only those structures the developer has been granted access to.

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

### Sample Structure calls

<br />

> [!CAUTION]
> **Caution:** Code examples shown here may be implemented differently than those of any sample apps, in order to illustrate basic API calls.

<br />

#### Get a set of structures

Calling `list()` on a `Query<Structure>` returns the most recent Set of
elements:

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

When designing a reactive app, you'll want to use `batched()` and `stream()`
calls rather than `list()`, because these automatically produce data when the
home graph changes.

#### Get structure properties

With the list of structures in hand, you can access the properties for them:

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

#### Find a structure by name

If you know the name of a structure, you can also access it using the `name`
property:

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

From there, properties, rooms, and devices for each structure are accessible.

#### Work with multiple structures

To use more than one structure, get a separate reference to each structure:

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

### Rooms

A room contains a group of devices. A room is always part of a structure and a
structure may have multiple rooms. Removing a room from a structure does not
remove the devices in that room from the structure. However, if the room is
deleted, the devices in that room become unassigned.

Use [`Home.rooms()`](https://developers.home.google.com/reference/swift/GoogleHomeSDK/Classes/Home.md.txt#rooms) to
retrieve all the rooms in the account, then use `roomID = device.roomID` to
display the corresponding devices in each room.

```swift
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)
```

### Sample Room calls

#### Get a list of rooms

Using the `Home` class, you can get a list of rooms and access the
properties for them:

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

#### Create a room

To create a new room in a `Structure`:

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

#### Delete a room

Or, alternatively, you can delete a room:

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

You can also delete a room using its ID:

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

If a room with devices is deleted, the devices will still be in the structure
but no longer assigned to a room.

#### Move devices to a different room

`Structure` also lets you move a device to a different room:

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

#### Change the name of a room

Call the
[`setName(_:)`](https://developers.home.google.com/reference/swift/GoogleHomeSDK/Structs/Room.md.txt#setname_)
method to change the name of a room:

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

When changing the name of a room, the original `Room` struct stays the same and
the change is reflected in the returned updated `Room` object.

Names will be truncated if over the 60 Unicode code point
(character) limit and no errors will be thrown. Developers are responsible for handling long names
and, for example, can decide if they want to inform users names will be truncated.

## Device groups

A device group represents a user-defined collection of devices within a single
structure. Group membership is retained in Google services and is visible to
other members of the structure and other apps with access to the structure.

Like physical rooms, device groups belong to a single structure. But unlike
rooms, device groups are logical groupings. A device can belong to multiple
device groups simultaneously. Device groups can contain up to 100
devices.
Device groups are represented by the [`DeviceGroup`](https://developers.home.google.com/reference/swift/GoogleHomeSDK/Structs/DeviceGroup.md.txt) entity, which is distinct from [`HomeDevice`](https://developers.home.google.com/reference/swift/GoogleHomeSDK/Structs/HomeDevice.md.txt). To interact with device groups, include the relevant device types and traits when configuring [`ReferencedAutomationTypes`](https://developers.home.google.com/reference/swift/GoogleHomeSDK/Classes/ReferencedAutomationTypes.md.txt) in `Home.configure`, along with the [`GroupTrait`](https://developers.home.google.com/reference/swift/GoogleHomeTypes/Enums/Google/GroupTrait), [`GroupManagementTrait`](https://developers.home.google.com/reference/swift/GoogleHomeTypes/Enums/Google/GroupManagementTrait.md.txt), and [`GroupMembershipTrait`](https://developers.home.google.com/reference/swift/GoogleHomeTypes/Enums/Google/GroupMembershipTrait.md.txt) traits.

You can interact with the devices in a group either by iterating over its
member devices directly (see
[Interact with group member devices](https://developers.home.google.com/apis/ios/structure.md.txt#interact-group-members)) or by targeting
a device type part on the `DeviceGroup` (see
[Control a device group](https://developers.home.google.com/apis/ios/device/control.md.txt#control-group)).

### Get a list of device groups

To query all device groups in the home:

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

### Interact with group member devices

Iterating over a group's member devices gives you fine-grained control over
individual members, lets you batch commands in a single request, and lets you
aggregate or write attributes across members.

First, filter the devices in the home by checking each device's
`GroupMembershipTrait` for the target group ID:

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

To send a single batched request with commands for a trait across all
compatible members of a group:

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

You can also update an attribute across all matching member devices in a group
(for example, setting `onTransitionTime` to `1` on all
`DimmableLightDeviceType` members) concurrently and await completion within a
deadline:

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

To aggregate an attribute across members of a group, such as finding the
minimum `colorTempPhysicalMaxMireds` across all color temperature lights in the
group:

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

### Create a device group

Use
[`GroupManagementTrait`](https://developers.home.google.com/reference/swift/GoogleHomeTypes/Enums/Google/GroupManagementTrait.md.txt)
on the structure to create a device group:

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

### Manage group membership

To add members to an existing group:

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

To remove members from a group:

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

### Change the name of a device group

To rename a device group, update the `GroupTrait`'s `name` field:

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

### Delete a device group

To delete a device group from a structure:

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

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

> [!NOTE]
> **Note:** Deleting a device group removes the group container, but does not remove or unpair the member devices from the structure.