Control devices

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 command:

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

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

// 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:

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 to handle it in your app. Alternatively, use flows as described in Observe state, which is the preferred method.

Send a command with parameters

Some commands may use parameters, like those on the OnOff or LevelControl traits:

offWithEffect

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

moveToLevel

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

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

For example, the step command for the FanControl trait has two optional arguments:

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 attribute:

// 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:

// 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.

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

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.

To set an attribute value, call the trait's update function 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.

For example:

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