The Discovery API for Android is intended to be used by apps that can create automations based on the devices present in the user's home. It can reveal to an app at runtime what traits and devices are present in a given structure for use in automations. In addition, it exposes the associated commands, attributes, and events, as well as the range of values that are allowed for parameters and fields.

The Discovery API ignores any devices or traits that exist in a structure that aren't supported by the Automation API, as well as any devices or traits that weren't registered in the[`FactoryRegistry`](https://developers.home.google.com/reference/kotlin/com/google/home/FactoryRegistry). See[Initialize the home on Android](https://developers.home.google.com/apis/android/initialize)for more information on using`FactoryRegistry`.
| **Caution:** Not all devices report state changes as they should. Devices that don't reliably report state changes shouldn't be used as starters. The Discovery API filters out devices that have been identified as having this issue. However, not all such devices may have been identified, therefore the Discovery API can't be viewed as infallible in this regard.
| **Important:** Discovery API calls can be resource-intensive and incur significant latency. Developers should be careful not to allow this latency to impact user interface responsiveness.
| **Caution:**
|
| - When using versions of the Home APIs earlier than 1.0.1 withGoogle Play servicesversion 25.02.34 or earlier, the Discovery API may throw an[`android.os.TransactionTooLargeException`](https://developer.android.com/reference/android/os/TransactionTooLargeException)if the structure contains 50 or more devices.
| - When using Home APIs version 1.1.0 or later withPlay servicesversion 25.03.32 or later, and the structure contains more than 200 devices, the Discovery API may simply return no candidates.

## Use the API

At the core of the Discovery API is the[`HasCandidates`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/HasCandidates)interface, which is the root of a type hierarchy that includes`Structure`,`Room`, and`HomeDevice`.

The[`HasCandidates`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/HasCandidates)interface defines two methods,`candidates()`and`allCandidates()`that both return`Flow`objects.

- `candidates()`produces a list of automation candidates for the entity (`Structure`,`Room`,`HomeDevice`).

- `allCandidates()`produces a list of automation candidates for the entity and all its children. This method is not supported by`Room`.

Unlike the other`Flow`objects returned by the Home APIs, these contain a one-time snapshot.

**To obtain the most up-to-date list of available candidates, the developer must call`candidates()`or`allCandidates()`each time, and cannot just call`collect()`on the`Flow`objects. Furthermore, because these two methods are especially resource-intensive, calling them more often than once per minute will result in cached data being returned, which may not reflect the actual current state at that moment.**

The[`NodeCandidate`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/NodeCandidate)interface represents a candidate node found by these two methods, and is the root of a hierarchy which includes the following interfaces:

- [`ActionCandidate`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/ActionCandidate)
- [`StarterCandidate`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/StarterCandidate)
- [`StateReaderCandidate`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/StateReaderCandidate)

and the following classes:

- [`CommandCandidate`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/CommandCandidate)
- [`EventCandidate`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/EventCandidate)
- [`TraitAttributesCandidate`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/TraitAttributesCandidate)

## Work with automation candidates

Say you're writing an app that creates an automation to close a set of smart blinds at a user-specified time. However, you don't know whether the user*has* a device that supports the`WindowCovering`trait and whether`WindowCovering`or any of its attributes or commands can be used in automations.

The following code illustrates how to use the Discovery API to filter the output of the`candidates()`method to narrow down the results and obtain the specific kind of element (structure, event, command) being sought. At the end, it creates an automation out of the collected elements.  

    import com.google.home.Structure
    import com.google.home.automation.CommandCandidate
    import com.google.home.automation.EventCandidate
    import com.google.home.automation.Automation
    import com.google.home.automation.DraftAutomation
    import com.google.home.platform.Time
    import java.time.LocalTime
    import com.google.home.matter.standard.WindowCoveringTrait
    import kotlinx.coroutines.flow.first
    import kotlinx.coroutines.runBlocking

    fun createAutomationWithDiscoveryApiTimeStarter(
      structureName: String,
      scheduledTimeInSecond: Int,
    ): String = runBlocking {

      // get the Structure
      val structure = homeClient.structures().findStructureByName(structureName)

      // get an event candidate
      val clockTimeStarter =
        structure
          .allCandidates().first().firstOrNull {candidate ->
            candidate is EventCandidate && candidate.eventFactory == Time.ScheduledTimeEvent
          } as EventCandidate

      // retrieve the first 'DownOrClose' command encountered
      val downOrCloseCommand =
        structure.allCandidates().first().firstOrNull {
            candidate ->
          candidate is CommandCandidate
            && candidate.commandDescriptor == WindowCoveringTrait.DownOrCloseCommand
        } as CommandCandidate

      val blinds = ...

      // prompt user to select the WindowCoveringDevice
      ...

    if (clockTimeStarter && downOrCloseCommand && blinds) {
      // Create the draft automation
      val draftAutomation: DraftAutomation = automation {
        name = ""
        description = ""
        isActive = true
        sequential {
          val mainStarter = starter<_>(structure, Time.ScheduledTimeEvent) {
              parameter(
                Time.ScheduledTimeEvent.clockTime(
                  LocalTime.ofSecondOfDay(scheduledTimeInSecond.toLong())
                )
              )
            }
          action(blinds, WindowCoveringDevice) { command(WindowCoveringTrait.downOrClose())
        }
      }

      // Create the automation in the structure
      val automation = structure.createAutomation(draftAutomation)
      return@runBlocking automation.id.id
    } else  ... //the automation cannot be created

The following example creates an automation to set the brightness level of a light when it is turned on.  

    import com.google.home.Structure
    import com.google.home.automation.CommandCandidate
    import com.google.home.automation.TraitAttributesCandidate
    import com.google.home.automation.Automation
    import com.google.home.automation.DraftAutomation
    import com.google.home.matter.standard.LevelControl
    import com.google.home.matter.standard.LevelControlTrait.MoveToLevelCommand
    import com.google.home.matter.standard.OnOff
    import com.google.home.matter.standard.OnOff.Companion.onOff
    import kotlinx.coroutines.flow.first
    import kotlinx.coroutines.runBlocking
    import com.google.home.automation.equals

    fun createAutomationWithDiscoveryApiDimLight(
      structureName: String,
    ): String = runBlocking {

      // get the Structure
      val structure: Structure = homeClient.structures().findStructureByName(structureName)

      /**
       * When I turn on the light, move the brightness level to 55
       */
      val allCandidates = structure.allCandidates().first()
      val dimmableLightDevice = structure.devices().list().first {it.has(OnOff) && it.has(LevelControl)}
      val starterCandidate =
        allCandidates
          .filterIsInstance<TraitAttributesCandidate>()
          .first { it.entity == dimmableLightDevice && it.trait == OnOff }

      val actionCandidate =
        allCandidates
          .filterIsInstance<CommandCandidate>()
          .first {it.entity == dimmableLightDevice && it.commandDescriptor == MoveToLevelCommand }

      if (starterCandidate && actionCandidate) {
        // Create the draft automation
        val draftAutomation: DraftAutomation = automation {
          sequential {
            val starter =  starter<_>(dimmableLightDevice, OnOffLightDevice, OnOff)
            condition { expression = starter.onOff equals true }
            action(dimmableLightDevice,DimmableLightDevice) {
              mapOf(MoveToLevelCommand.Request.CommandFields.level to 55u.toUByte())
            )
          }
        }

        // Create the automation in the structure
        val automation = structure.createAutomation(draftAutomation)
        return@runBlocking automation.id.id
        }
    } else ... //the automation cannot be created

## Verify that a trait attribute supports your use case

While all trait attributes can be used as a state reader, not all trait attributes can be used as a starter, and not all trait attributes can be modified. You should check each attribute that you intend to use to see whether it supports what you want to do with it.

The[`TraitAttributesCandidate`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/TraitAttributesCandidate)is the starting point for checking attributes. This class represents a trait. Each trait has one or more attributes associated with it. The trait's attributes are found in the[`TraitAttributesCandidate.fieldDetailsMap`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/TraitAttributesCandidate#fieldDetailsMap()), which is a map of[`AttributeDetails`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/AttributeDetails)instances, each one representing one of the trait's attributes.

So, to determine what an attribute supports:

1. Get a handle to the[`TraitAttributesCandidate`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/TraitAttributesCandidate)that represents a trait that you want to use in your automation.
2. Retrieve the trait's attributes through the[`TraitAttributesCandidate.fieldDetailsMap`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/TraitAttributesCandidate#fieldDetailsMap()).
3. Check the`AttributeDetails`instance for the attribute you're interested in:
   1. To determine whether the attribute can be used as a starter, check[`AttributeDetails.isSubscribable`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/AttributeDetails#isSubscribable()).
   2. To determine whether an attribute can be updated, check[`AttributeDetails.isModifiable`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/AttributeDetails#isModifiable()).

## Check for prerequisites

The Discovery API lets you know a trait is missing a prerequisite for use, such as a subscription or a structure address. It does this using the`Candidate`class's[`unsupportedReasons`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/NodeCandidate#unsupportedReasons())attribute. This attribute is populated with an[`UnsupportedCandidateReason`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/UnsupportedCandidateReason)during the`candidates()`call. And the same information appears in the validation error messages when`createAutomation()`is called.

Example reasons:

- [`MissingStructureAddressSetup`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/MissingStructureAddressSetup)lets the user know that address setup is required in order to use the`Time`trait.[Change Google home address](https://support.google.com/googlenest/answer/7551002)explains how a user can enter the structure address using theGoogle Home app (GHA).
- [`MissingPresenceSensingSetup`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/MissingPresenceSensingSetup)lets the user know that presence setup is required in order to use`AreaPresenceState`and`AreaAttendanceState`traits.
- [`MissingSubscription`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/MissingSubscription)lets the user know that a[Nest Aware](https://store.google.com/product/nest_aware)subscription is required in order to use the`ObjectDetection`trait.

For example, to handle the`MissingStructureAddressSetup``UnsupportedCandidateReason`, you might want to show a[toast](https://developer.android.com/guide/topics/ui/notifiers/toasts)in your app and open theGHAto allow the user to provide the address of the structure:  

    val structure = homeManager.structures().list().single()
    val allCandidates = structure.allCandidates().list().single()
    val scheduledStarterCandidate = allCandidates.first { it is EventCandidate && it.eventFactory == ScheduledTimeEvent }
    if (scheduledStarterCandidate.unsupportedReasons.any { it is MissingStructureAddressSetup }) {
      showToast("No Structure Address setup. Redirecting to GHA to set up an address.")
      launchChangeAddress(...)
    }

## Validate parameters

The Discovery API returns the values that are allowed for an attribute, parameter, or event field, in the form of a[`Constraint`](https://developers.home.google.com/reference/kotlin/com/google/home/automation/Constraint)instance. This information allows the app developer to prevent users from setting invalid values.

Each of the subclasses of`Constraint`have their own way to represent accepted values.

|                                                       Constraint class                                                        |                      Properties representing accepted values                       |
|-------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
| [BitmapConstraint](https://developers.home.google.com/reference/kotlin/com/google/home/automation/BitmapConstraint)           | `combinedBits`                                                                     |
| [BooleanConstraint](https://developers.home.google.com/reference/kotlin/com/google/home/automation/BooleanConstraint)         |                                                                                    |
| [ByteConstraint](https://developers.home.google.com/reference/kotlin/com/google/home/automation/ByteConstraint)               | `maxLength`and`minLength`                                                          |
| [EnumConstraint](https://developers.home.google.com/reference/kotlin/com/google/home/automation/EnumConstraint)               | `allowedSet`                                                                       |
| [NumberRangeConstraint](https://developers.home.google.com/reference/kotlin/com/google/home/automation/NumberRangeConstraint) | `lowerBound`,`upperBound`,`step`and`unit`                                          |
| [NumberSetConstraint](https://developers.home.google.com/reference/kotlin/com/google/home/automation/NumberSetConstraint)     | `allowedSet`and`unit`                                                              |
| [StringConstraint](https://developers.home.google.com/reference/kotlin/com/google/home/automation/StringConstraint)           | `allowedSet`,`disallowedSet`,`isCaseSensitive`,`maxLength`,`minLength`, and`regex` |
| [StructConstraint](https://developers.home.google.com/reference/kotlin/com/google/home/automation/StructConstraint)           | `fieldConstraints`                                                                 |
| [ListConstraint](https://developers.home.google.com/reference/kotlin/com/google/home/automation/ListConstraint)               | `elementConstraint`                                                                |
[*Table: Types of`Constraint`*]

### Use constraints

Say you're writing an app that creates an automation that sets the level of a device with the[`LevelControl`](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/LevelControl)trait. The following example shows how you would ensure that the value used to set the`LevelControl`trait's[`currentLevel`](https://developers.home.google.com/reference/kotlin/com/google/home/matter/standard/LevelControlTrait.Attributes#currentLevel())attribute is within the accepted bounds.  


    import android.content.Context
    import com.google.home.Home
    import com.google.home.Structure
    import com.google.home.automation.Action
    import com.google.home.automation.Automation
    import com.google.home.automation.CommandCandidate
    import com.google.home.automation.Condition
    import com.google.home.automation.Constraint
    import com.google.home.automation.Equals
    import com.google.home.automation.EventCandidate
    import com.google.home.automation.HasCandidates
    import com.google.home.automation.Node
    import com.google.home.automation.NodeCandidate
    import com.google.home.automation.SequentialFlow
    import com.google.home.automation.Starter
    import com.google.home.matter.standard.LevelControlTrait

    // Filter the output of candidates() to find the TraitAttributesCandidate
    // for the LevelControl trait.

    val levelCommand =
            structure
              .allCandidates()
              .first()
              .firstOrNull { candidate ->
                candidate is CommandCandidate && candidate.command == LevelControlTrait.MoveToLevelCommand
     } as? CommandCandidate

    var levelConstraint = null

    // Get the NodeCandidate instance's fieldDetailsMap and
    // retrieve the Constraint associated with the level parameter.
    // In this case, it is a NumberRangeConstraint.
    if (levelCommand != null) {
        levelConstraint =
          levelCommand.fieldDetailsMap[
            LevelControlTrait.MoveToLevelCommand.Request.CommandFields.level
            ]!!.constraint
    }

    ...

    // Test the value against the Constraint (ignoring step and unit)
    if ( value in levelConstraint.lowerBound..levelConstraint.upperBound) {
       // ok to use the value
    }

## Compare the Device API and Discovery API

You can discover device types, traits, and their attributes without using the Discovery API. Using the Device API, you can discover:

1. The primary device types the user has granted permission to the developer for control, using the[`DeviceType.Metadata.isPrimaryType()`](https://developers.home.google.com/reference/kotlin/com/google/home/DeviceType.Metadata#isPrimaryType())method.
2. Whether each device supports all the traits the automation requires, using the[`HasTraits.has()`](https://developers.home.google.com/reference/kotlin/com/google/home/HasTraits#has(com.google.home.TraitFactory))method.
3. Whether each trait supports all[attributes](https://developers.home.google.com/apis/android/device#check-attribute-support)and[commands](https://developers.home.google.com/apis/android/device#check-command-support)the automation requires, using the`supports()`method.

It's important to note that if you use the Device API to do discovery, you don't benefit from the following Discovery API capabilities:

- Automatic filtering out of traits that aren't supported by the Automation API.
- The ability to provide users with an option to select a valid value for those attributes and parameters that use constraints.