## Check if a trait supports a command

Support can also be checked for a trait command. Also use the trait-level
`supports` function to check if a command is supported for a particular device.

For example, to check for a device's support of the On/Off trait's
[`toggle`](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/OnOff.Command#Toggle)
command:

```kotlin
// Check if the OnOff trait supports the toggle command.
if (onOffTrait.supports(OnOff.Command.Toggle)) {
  println("onOffTrait supports toggle command")
} else {
  println("onOffTrait does not support stateful toggle command")
}
```

> [!NOTE]
> **Note:** The value for the `Command` enum is capitalized (`Toggle`), while the command method is not (`toggle`).

## Send a command to a device

Sending a command is similar to reading a state attribute from a trait. To
turn the device on or off, use the
[`OnOff`](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/OnOffTrait.ToggleCommand)
trait's Toggle command, which is defined in the Google Home ecosystem data
model as `toggle()`. This method changes `onOff` to `false` if it is `true`, or
to `true` if it is `false`:

```kotlin
// Calling a command on a trait.
try {
  onOffTrait.toggle()
} catch (e: HomeException) {
  // Code for handling the exception
}
```

**All trait commands are `suspend` functions and only complete when a response
is returned by the API (such as confirming the device state has changed).
Commands might return an exception if an issue is detected with the
execution flow. As a developer, you should use a `try-catch` block to properly
handle these exceptions, and surface detailed information to users on cases where
the errors are actionable. Unhandled exceptions will stop the app runtime and
can result in crashes in your app.**

Alternatively, use the `off()` or `on()` commands to explicitly set the state:

```kotlin
onOffTrait.off()
onOffTrait.on()
```

After sending a command to change the state, once it completes you can read the
state as described in [Read a device state](https://developers.home.google.com/apis/android/device#read-state) to
handle it in your app. Alternatively, use flows as described in [Observe
state](https://developers.home.google.com/apis/observe-state), which is the preferred method.

## Send a command with parameters

Some commands may use parameters, like those on the
[`OnOff`](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/OnOff) or
[`LevelControl`](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/LevelControl)
traits:

### offWithEffect

```kotlin
// Turn off the light using the DyingLight effect.
onOffTrait.offWithEffect(
  effectIdentifier = OnOffTrait.EffectIdentifierEnum.DyingLight,
  effectVariant = 0u,
)
```

### moveToLevel

```kotlin
// Change the brightness of the light to 50%
levelControlTrait.moveToLevel(
  level = 127u.toUByte(),
  transitionTime = null,
  optionsMask = LevelControlTrait.OptionsBitmap(),
  optionsOverride = LevelControlTrait.OptionsBitmap(),
)
```

> [!NOTE]
> **Note:** Per the Matter Specification, the `currentLevel` attribute has a range of 0 (or 1) to 254. Thus to set the brightness level to approximately 50%, use a value of 127.

Some commands have optional arguments, which come after the required arguments.

For example, the [`step` command for the `FanControl`
trait](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/FanControlTrait.StepCommand.Request)
has two optional arguments:

```kotlin
val fanControlTraitFlow: Flow<FanControl?> =
  device.type(FanDevice).map { it.standardTraits.fanControl }.distinctUntilChanged()

val fanControl = fanControlTraitFlow.firstOrNull()

// Calling a command with optional parameters not set.
fanControl?.step(direction = FanControlTrait.StepDirectionEnum.Increase)

// Calling a command with optional parameters.
fanControl?.step(direction = FanControlTrait.StepDirectionEnum.Increase) { wrap = true }
```

## Check if a trait supports an attribute

Some devices may support a Matter trait, but not a
specific attribute. For example, a Cloud-to-cloud device that was
mapped to Matter may not support every
Matter attribute. To handle cases like these, use the
trait-level `supports` function and the trait's `Attribute` enum to check if
the attribute is supported for a particular device.

For example, to check for a device's support of the On/Off trait's
[`onOff`](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/OnOff.Attribute#onOff)
attribute:

```kotlin
// Check if the OnOff trait supports the onOff attribute.
if (onOffTrait.supports(OnOff.Attribute.onOff)) {
  println("onOffTrait supports onOff state")
} else {
  println("onOffTrait is for a command only device!")
}
```

Some attributes are nullable in the Matter specification or
the Cloud-to-cloud smart home schema. For these
attributes, you can determine whether a **null** returned by the attribute is
due to the device not reporting that value, or if the attribute's value
actually is `null`, by using `isNullable` in addition to `supports`:

```kotlin
// Check if a nullable attribute is set or is not supported.
if (onOffTrait.supports(OnOff.Attribute.startUpOnOff)) {
  // The device supports startupOnOff, it is safe to expect this value in the trait.
  if (OnOff.Attribute.startUpOnOff.isNullable && onOffTrait.startUpOnOff == null) {
    // This value is nullable and set to null. Check the specification as to
    // what null in this case means
    println("onOffTrait supports startUpOnOff and it is null")
  } else {
    // This value is nullable and set to a value.
    println("onOffTrait supports startUpOnOff and it is set to ${onOffTrait.startUpOnOff}")
  }
} else {
  println("onOffTrait does not support startUpOnOff!")
}
```

## Update trait attributes

If you want to change the value of a given attribute, and none of the trait's
commands does so, the attribute may support having its value explicitly set.

Whether the value of an attribute can be changed depends on two factors:

- Is the attribute writable?
- Can the value of the attribute change as a side effect of sending a trait command?

The reference documentation for traits and their attributes provides this
information.

> [!NOTE]
> **Note:** Commands are not directly associated with an attribute.

Therefore, the combinations of properties that dictate how an attribute's
value might be changed are:

- Read-only and *not affected* by other commands. This means that the
  attribute's value does not change. For example, the
  [`currentPosition` attribute of the `Switch` trait](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/SwitchTrait.Attributes#currentPosition()).

- Read-only and *affected* by other commands. This means that the only way the
  attribute's value can change is as the result of sending a command.
  For example, the
  [`currentLevel` attribute of the `LevelControl` Matter trait](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/LevelControlTrait.Attributes#currentLevel())
  is read-only, but its value can be mutated by commands such as
  [`moveToLevel`](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/LevelControlTrait.MoveToLevelCommand).

- Writeable and *not affected* by other commands. This means that you can
  directly change the value of the attribute by using the
  `update` function of the trait, but there are no commands that will affect
  the value of the attribute. For example, the
  [`WrongCodeEntryLimit` attribute of the `DoorLock` trait](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/DoorLockTrait.Attributes#wrongCodeEntryLimit()).

- Writeable and *affected* by other commands. This means that you can
  directly change the value of the attribute by using the `update` function
  of the trait, and the attribute's value can change as the result of
  sending a command. For example, the
  [`speedSetting` attribute of the `FanControlTrait`](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/FanControlTrait.Attributes#speedSetting())
  can be written to directly, but is also mutable using the
  [`step` command](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/FanControlCommands#step(com.google.home.matter.standard.FanControlTrait.StepDirectionEnum,kotlin.Function1)).

## Example of using the update function to change an attribute's value

This example shows how to explicitly set the value of the
[`DoorLockTrait.WrongCodeEntryLimit` attribute](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/DoorLockTrait.Attributes#wrongCodeEntryLimit()).

To set an attribute value, call the trait's [`update` function](https://developers.home.google.com/reference/kotlin/com/google/home/Updatable#update) and pass it a mutator function that sets the new value.
It's a good practice to first
[verify that the trait supports an attribute](https://developers.home.google.com/apis/android/device#check-attribute-support).

For example:

```kotlin
    val doorLockDevice = home.devices().list().first { device -> device.has(DoorLock) }

    val traitFlow: Flow<DoorLock?> =
      doorLockDevice.type(DoorLockDevice).map { it.standardTraits.doorLock }.distinctUntilChanged()

    val doorLockTrait: DoorLock = traitFlow.first()!!

    if (doorLockTrait.supports(DoorLock.Attribute.wrongCodeEntryLimit)) {
      val unused = doorLockTrait.update { setWrongCodeEntryLimit(3u) }
    }
```

## Send multiple commands at once

> [!WARNING]
> **Warning:** This API is experimental and may change or be removed without notice.

The Batching API allows a client to send multiple Home APIs device
commands in a single payload. The commands are batched into a single payload and
executed in parallel, similar to how one might construct a Home API
[automation](https://developers.home.google.com/apis/android/automation) using the
[parallel node](https://developers.home.google.com/apis/android/automation/dsl/complex#sequential_with_multiple_parallel_actions),
such as the
[Open blinds before sunrise](https://developers.home.google.com/apis/android/automation/examples#open-blinds-before-sunrise)
example. However, the Batching API allows for more complex and sophisticated
behaviors than the Automation API, such as the ability to dynamically select
devices at runtime according to any criteria.

The commands in one batch can target multiple traits across multiple devices,
in multiple rooms, in multiple structures.

Sending commands in a batch allows devices to perform actions simultaneously,
which isn't really possible when commands are sent sequentially in separate
requests. The behavior achieved using batched commands allows the developer to
set the state of a group of devices to match a predetermined aggregate state.

### Use the Batching API

There are three basic steps involved in invoking commands through the Batching
API:

1. Invoke the [`Home.sendBatchedCommands()`](https://developers.home.google.com/reference/kotlin/com/google/home/Home#sendBatchedCommands(kotlin.Function1)) method.
2. Within the body of the `sendBatchedCommands()` block, specify the commands to be included in the batch.
3. Check the results of the sent commands to see whether they succeeded or failed.

#### Send batched commands

Call the
[`Home.sendBatchedCommands()`](https://developers.home.google.com/reference/kotlin/com/google/home/Home#sendBatchedCommands(kotlin.Function1))
method. Behind the scenes, this method sets up a lambda expression in a special
batch context.

    home.sendBatchedCommands() {

### Specify batch commands

Within the body of the `sendBatchedCommands()` block, populate *batchable
commands.* Batchable commands are "shadow" versions of existing Device API
commands that can be used in a batch context, and are named with the added
suffix *`Batchable`* . For example, the
[`LevelControl`](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/LevelControl)
trait's
[`moveToLevel()`](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/LevelControlCommands#moveToLevel(kotlin.UByte,kotlin.UShort,com.google.home.matter.standard.LevelControlTrait.OptionsBitmap,com.google.home.matter.standard.LevelControlTrait.OptionsBitmap))
command has a counterpart named
[`moveToLevelBatchable()`](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/LevelControlCommands#moveToLevelBatchable(kotlin.UByte,kotlin.UShort,com.google.home.matter.standard.LevelControlTrait.OptionsBitmap,com.google.home.matter.standard.LevelControlTrait.OptionsBitmap)).

Example:

      val response1 = add(command1)

      val response2 = add(command2)

The batch is automatically sent once all commands have been added to the
batch context and execution has left the context.

Responses are captured in
[`DeferredResponse<T>`](https://developers.home.google.com/reference/kotlin/com/google/home/DeferredResponse)
objects.

> [!IMPORTANT]
> **Important:** During batch execution, if an individual command fails, no exception is thrown.

The [`DeferredResponse<T>`](https://developers.home.google.com/reference/kotlin/com/google/home/DeferredResponse)
instances can be gathered into an object of any type, such as a
`Collection`, or a data class that you define. Whatever type of object you
choose to assemble the responses is what is returned by
`sendBatchedCommands()`. For example, the batch context can return two
`DeferredResponse` instances in a `Pair`:

      val (response1, response2) = homeClient.sendBatchedComamnds {
        val response1 = add(someCommandBatched(...))
        val response2 = add(someOtherCommandBatched(...))
        Pair(response1, response2)
      }

Alternatively, the batch context can return the `DeferredResponse`
instances in a custom data class:

      // Custom data class
      data class SpecialResponseHolder(
        val response1: DeferredResponse<String>,
        val response2: DeferredResponse<Int>,
        val other: OtherResponses
      )
      data class OtherResponses(...)

#### Check each response

Outside the `sendBatchedCommands()` block, check the responses to determine
whether the corresponding command succeeded or failed. This is done by calling
`DeferredResponse.getOrThrow()`, which either:
- returns the result of the executed command,
- or, if the batch scope has not completed or the command was
unsuccessful, throws an error.

You should only check the results *outside* of the `sendBatchedCommands()`
lambda scope.

### Example

Say you wanted to build an app that uses the Batching API to set up a 'good
night' scene that configures all the devices in the home for nighttime, when
everyone is asleep. This app should turn out the lights and lock the front
and back doors.

Here's one way to approach the task:

    val lightDevices: List<OnOffLightDevice>
    val doorlockDevices: List<DoorLockDevice>

    // Send all the commands
    val responses: List<DeferredResponse<Unit>> = home.sendBatchedCommands {
      // For each light device, send a Batchable command to turn it on
      val lightResponses: List<DeferredResponse<Unit>> = lightDevices.map { lightDevice ->
        add(lightDevice.standardTraits.onOff.onBatchable())
      }

      // For each doorlock device, send a Batchable command to lock it
      val doorLockResponse: List<DeferredResponse<Unit>> = doorlockDevices.map { doorlockDevice ->
        add(doorlockDevice.standardTraits.doorLock.lockDoorBatchable())
      }

      lightResponses + doorLockResponses
    }

    // Check that all responses were successful
    for (response in responses) {
      response.getOrThrow()
    }

## Control a device group

> [!NOTE]
> **Note:** Device group APIs require the `@HomeExperimentalApi` annotation on Android.

In addition to fine-grained control over every member of a group by [iterating
through its member devices](https://developers.home.google.com/apis/android/structure#interact-group-members),
you can control a
[`DeviceGroup`](https://developers.home.google.com/reference/kotlin/com/google/home/DeviceGroup) as a single
entity. `DeviceGroup` implements the
[`PartsProvider`](https://developers.home.google.com/reference/kotlin/com/google/home/PartsProvider) interface,
which exposes the [`DeviceType`](https://developers.home.google.com/reference/kotlin/com/google/home/DeviceType)
parts supported by the devices in the group:

- `group.parts()` returns a `Flow<Set<DeviceType>>` of all device types present in the group.
- `group.part()` and `group.partOrNull()` return a flow for a specific device type, such as `OnOffLightDevice` or `DimmableLightDevice`.

When you invoke a trait command on a device type part of a `DeviceGroup`, the
SDK relays the command to all associated member devices in the group that
support that capability, regardless of their underlying connectivity or
transport (such as Matter or Cloud-to-cloud).

### Group part trait attributes and error handling

Targeting a group part simplifies common cases where you want to send an
idempotent command (such as turning lights on or off, or setting a brightness
level) across a group. When working with traits and commands on a `DeviceGroup`
part, keep the following behaviors in mind:

- Dynamic state attributes (such as the `onOff` attribute of the `OnOff` trait or the `currentLevel` attribute of the `LevelControl` trait) are generally not populated on a group part's trait because member devices in a group can be in different states that cannot be combined into a single value. However, supported commands (checked with `supports()`, such as `LevelControl.Command.MoveToLevelWithOnOff` on a dimmable light) and select capability attributes (such as `colorTempPhysicalMinMireds` and `colorTempPhysicalMaxMireds` on `ColorControl` for color lights) are populated so that your app can configure UI controls appropriately.
- Not all commands on traits of group members are available on a group part. In particular, commands that return values are not available.
- A command on a group part does not return individual results for each underlying member device command. To inspect individual command results or call commands that return values, [iterate through the group's member
  devices](https://developers.home.google.com/apis/android/structure#interact-group-members) directly.
- Commands sent to a part on a `DeviceGroup` complete once the entire fanout across member devices finishes and all underlying command successes or failures are collected. A command only fails (throws a [`HomeException`](https://developers.home.google.com/reference/kotlin/com/google/home/HomeException)) if the command fails for every associated member device or if all target devices are unavailable, surfacing as a single command failure matching the single-command call syntax. If at least one member device executes the command, the group command completes without throwing an exception.

### Send commands to a group part

To turn on all `OnOffLightDevice` lights in a device group:

    val lightPart = group.partOrNull(OnOffLightDevice).firstOrNull()
    val onOffTrait = lightPart?.trait(OnOff)

    if (onOffTrait?.supports(OnOff.Command.On) == true) {
      onOffTrait.on()
    }

You can also inspect all device types present in a group using `group.parts()`
to target a trait across multiple related device types (for example, turning
off any light device type in the group that supports
[`OnOff`](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/OnOff)):

    val lightTypes = listOf(
      OnOffLightDevice,
      DimmableLightDevice,
      ColorTemperatureLightDevice,
      ExtendedColorLightDevice,
    )

    val parts = group.parts().first()
    val onOffTrait = lightTypes.firstNotNullOfOrNull { type ->
      parts.firstOrNull { it.factory == type }
    }?.trait(OnOff)

    onOffTrait?.off()

You can check command support or read capability bounds on a group part before
sending a command (for example, checking `MoveToLevelWithOnOff` on a dimmable
light part or reading `colorTempPhysicalMinMireds` and
`colorTempPhysicalMaxMireds` on a color temperature light part):

    val dimmableLight = group.partOrNull(DimmableLightDevice).firstOrNull()
    val levelControl = dimmableLight?.trait(LevelControl)

    if (
      levelControl?.supports(
        LevelControl.Command.MoveToLevelWithOnOff
      ) == true
    ) {
      levelControl.moveToLevelWithOnOff(
        level = 127u,
        transitionTime = null,
        optionsMask = LevelControlTrait.OptionsBitmap(),
        optionsOverride = LevelControlTrait.OptionsBitmap(),
      )
    }

    val colorLight = group.partOrNull(ColorTemperatureLightDevice).firstOrNull()
    val colorControl = colorLight?.trait(ColorControl)
    val minMireds = colorControl?.colorTempPhysicalMinMireds
    val maxMireds = colorControl?.colorTempPhysicalMaxMireds

    if (colorControl != null && minMireds != null && maxMireds != null) {
      val midMireds = ((minMireds.toInt() + maxMireds.toInt()) / 2).toUShort()
      colorControl.moveToColorTemperature(
        colorTemperatureMireds = midMireds,
        transitionTime = 0u,
        optionsMask = ColorControlTrait.OptionsBitmap(),
        optionsOverride = ColorControlTrait.OptionsBitmap(),
      )
    }