Sfrutta il rilevamento dei dispositivi

L'API Discovery è progettata per essere utilizzata da app che possono creare automazioni basate sui dispositivi presenti nella casa dell'utente. Può rivelare a un'app in fase di esecuzione quali caratteristiche e dispositivi sono presenti in una determinata struttura per l'utilizzo nelle automazioni. Inoltre, espone i comandi, gli attributi e gli eventi associati, nonché l'intervallo di valori consentiti per i parametri e i campi.

L'API Discovery ignora i dispositivi o i trait esistenti in una struttura che non sono supportati dall'API Automation, nonché i dispositivi o i trait che non sono stati registrati in FactoryRegistry. Per ulteriori informazioni sull'utilizzo diFactoryRegistry, consulta la sezione Creare un'istanza Home.

Utilizzare l'API

L'interfaccia HasCandidates è al centro dell'API Discovery ed è la radice di una gerarchia di tipi che include Structure, Room e HomeDevice.

L'interfaccia HasCandidates definisce due metodi, candidates() e allCandidates(), che entrambi restaurano oggetti Flow.

  • candidates() genera un elenco di candidati per l'automazione per l'entità (Structure, Room, HomeDevice).

  • allCandidates() genera un elenco di candidati per l'automazione per l'entità e per tutti i relativi elementi secondari. Questo metodo non è supportato da Room.

A differenza degli altri oggetti Flow restituiti dalle API Home, questi contengono un snapshot una tantum.

Per ottenere l'elenco più aggiornato dei candidati disponibili, lo sviluppatore deve chiamare candidates() o allCandidates() ogni volta e non può semplicemente chiamare collect() sugli oggetti Flow. Inoltre, poiché questi due metodi sono particolarmente impegnativi in termini di risorse, se li chiami più di una volta al minuto, verranno restituiti i dati memorizzati nella cache, che potrebbero non riflettere lo stato attuale reale in quel momento.

L'interfaccia NodeCandidate rappresenta un nodo candidato trovato da questi due metodi ed è la radice di una gerarchia che include le seguenti interfacce:

e le seguenti classi:

Lavorare con i candidati all'automazione

Supponiamo che tu stia scrivendo un'app che crea un'automazione per chiudere un insieme di tende intelligenti a un'ora specificata dall'utente. Tuttavia, non sai se l'utente ha un dispositivo che supporta la caratteristica WindowCovering e se WindowCovering o uno dei suoi attributi o comandi può essere utilizzato nelle automazioni.

Il codice seguente illustra come utilizzare l'API Discovery per filtrare l'output del metodo candidates() per restringere i risultati e ottenere il tipo specifico di elemento (struttura, evento, comando) cercato. Al termine, viene creata un'automazione dagli elementi raccolti.

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

L'esempio seguente crea un'automazione per impostare il livello di luminosità di una luce quando viene accesa.

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

Controlla i prerequisiti

L'API Discovery ti consente di sapere che a un tratto manca un prerequisito per l'utilizzo, ad esempio un abbonamento o l'indirizzo di una struttura. Lo fa utilizzando l'attributo unsupportedReasons della classe Candidate. Questo attributo viene compilato con un UnsupportedCandidateReason durante la chiamata candidates(). Le stesse informazioni vengono visualizzate nei messaggi di errore di convalida quando viene chiamato createAutomation().

Motivi di esempio:

Ad esempio, per gestire MissingStructureAddressSetup UnsupportedCandidateReason, potresti mostrare un messaggio popup nella tua app e aprire GHA per consentire all'utente di fornire l'indirizzo della struttura:

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

Convalida i parametri

L'API Discovery restituisce i valori consentiti per un attributo, un parametro o un campo evento sotto forma di istanza Constraint. Queste informazioni consentono allo sviluppatore di app di impedire agli utenti di impostare valori non validi.

Ognuna delle sottoclassi di Constraint ha il proprio modo di rappresentare i valori accettati.

Tabella: tipi di Constraint
Classe di vincolo Proprietà che rappresentano i valori accettati
BitmapConstraint combinedBits
BooleanConstraint
ByteConstraint maxLength e minLength
EnumConstraint allowedSet
NumberRangeConstraint lowerBound, upperBound, step e unit
NumberSetConstraint allowedSet e unit
StringConstraint allowedSet, disallowedSet, isCaseSensitive, maxLength, minLength e regex
StructConstraint fieldConstraints
ListConstraint elementConstraint

Utilizzare i vincoli

Supponiamo che tu stia scrivendo un'app che crea un'automazione che imposta il livello di un dispositivo con la caratteristica LevelControl. L'esempio seguente mostra come assicurarti che il valore utilizzato per impostare l'attributo currentLevel della caratteristica LevelControl rientri nei limiti accettati.


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
}

Confrontare l'API Device e l'API Discovery

Puoi scoprire i tipi di dispositivi, le caratteristiche e i relativi attributi senza utilizzare l'API Discovery. Con l'API Device puoi scoprire:

  1. I tipi di dispositivi principali per i quali l'utente ha concesso all'autore dello sviluppo l'autorizzazione di controllo utilizzando il metodo DeviceType.Metadata.isPrimaryType().
  2. Se ogni dispositivo supporta tutte le caratteristiche richieste dall'automazione, utilizzando il metodo HasTraits.has().
  3. Se ogni tratto supporta tutti gli attributi e i comandi richiesti dall'automazione, utilizzando il metodo supports().

È importante notare che se utilizzi l'API Device per eseguire il rilevamento, non potrai usufruire delle seguenti funzionalità dell'API Discovery:

  • Filtro automatico dei tratti non supportati dall'API Automation.
  • La possibilità di offrire agli utenti un'opzione per selezionare un valore valido per gli attributi e i parametri che utilizzano vincoli.