Controllare i dispositivi su Android

Verificare se un tratto supporta un comando

L'assistenza può essere controllata anche per un comando del tratto. Utilizza anche la funzione supports a livello di tratto per verificare se un comando è supportato per un determinato dispositivo.

Ad esempio, per verificare se un dispositivo supporta il comando toggle del tratto On/Off:

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

Invio di un comando a un dispositivo

L'invio di un comando è simile alla lettura di un attributo stato da un tratto. Per accendere o spegnere il dispositivo, utilizza il comando Toggle della caratteristica OnOff, definito nel modello di dati dell'ecosistema Google Home come toggle(). Questo metodo cambia onOff in false se è true o in true se è false:

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

Tutti i comandi dei tratti sono funzioni suspend e vengono completati solo quando l'API restituisce una risposta (ad esempio la conferma che lo stato del dispositivo è cambiato). I comandi potrebbero restituire un'eccezione se viene rilevato un problema con il flusso di esecuzione. In qualità di sviluppatore, devi utilizzare un blocco try-catch per gestire correttamente queste eccezioni e mostrare agli utenti informazioni dettagliate nei casi in cui gli errori sono risolvibili. Le eccezioni non gestite interrompono il runtime dell'app e possono causare arresti anomali nell'app.

In alternativa, utilizza i comandi off() o on() per impostare in modo esplicito lo stato:

onOffTrait.off()
onOffTrait.on()

Dopo aver inviato un comando per modificare lo stato, al termine puoi leggere lo stato come descritto in Leggere lo stato di un dispositivo per gestirlo nella tua app. In alternativa, utilizza i flussi descritti in Osserva lo stato, che è il metodo preferito.

Inviare un comando con parametri

Alcuni comandi possono utilizzare parametri, ad esempio quelli dei tratti OnOff o LevelControl:

// Turn off the light using the DyingLight effect.
onOffTrait.offWithEffect(
  effectIdentifier = OnOffTrait.EffectIdentifierEnum.DyingLight,
  effectVariant = 0u,
)
// Change the brightness of the light to 50%
levelControlTrait.moveToLevel(
  level = 127u.toUByte(),
  transitionTime = null,
  optionsMask = LevelControlTrait.OptionsBitmap(),
  optionsOverride = LevelControlTrait.OptionsBitmap(),
)

Alcuni comandi hanno argomenti facoltativi, che si trovano dopo gli argomenti obbligatori.

Ad esempio, il comando step per il tratto FanControl ha due argomenti facoltativi:

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 }

Controllare se un tratto supporta un attributo

Alcuni dispositivi potrebbero supportare un tratto Matter, ma non un attributo specifico. Ad esempio, un dispositivo Cloud-to-cloud mappato a Matter potrebbe non supportare tutti gli attributi Matter. Per gestire casi come questi, utilizza la funzione supports a livello di tratto e l'enum Attribute del tratto per verificare se l'attributo è supportato per un determinato dispositivo.

Ad esempio, per verificare se un dispositivo supporta l'attributo onOff del tratto On/Off:

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

Alcuni attributi sono nullable nella specifica Matter o nello schema Cloud-to-cloud smart home. Per questi attributi, puoi determinare se un valore null restituito dall'attributo è dovuto al fatto che il dispositivo non lo segnali o se il valore dell'attributo è effettivamente null utilizzando isNullable oltre a 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!")
}

Aggiorna gli attributi dei tratti

Se vuoi modificare il valore di un determinato attributo e nessuno dei comandi del tratto lo fa, l'attributo potrebbe supportare l'impostazione esplicita del valore.

La possibilità di modificare il valore di un attributo dipende da due fattori:

  • L'attributo è scrivibile?
  • Il valore dell'attributo può cambiare come effetto collaterale dell'invio di un comando trait?

La documentazione di riferimento per le caratteristiche e i relativi attributi fornisce queste informazioni.

Pertanto, le combinazioni di proprietà che determinano in che modo il valore di un attributo può essere modificato sono:

  • Di sola lettura e non interessate da altri comandi. Ciò significa che il valore dell'attributo non cambia. Ad esempio, l'attributo currentPosition del tratto Switch.

  • Di sola lettura e interessato da altri comandi. Ciò significa che l'unico modo in cui il valore dell'attributo può cambiare è in seguito all'invio di un comando. Ad esempio, l'attributo currentLevel del tratto LevelControl Matter è di sola lettura, ma il suo valore può essere modificato da comandi come moveToLevel.

  • Scrivibili e non interessati da altri comandi. Ciò significa che puoi modificare direttamente il valore dell'attributo utilizzando la funzione update del tratto, ma non esistono comandi che influiscano sul valore dell'attributo. Ad esempio, l'attributo WrongCodeEntryLimit del tratto DoorLock.

  • Scrivibili e influiti da altri comandi. Ciò significa che puoi modificare direttamente il valore dell'attributo utilizzando la funzione update del tratto e il valore dell'attributo può cambiare come risultato dell'invio di un comando. Ad esempio, l'attributo occupiedCoolingSetpoint del tratto Thermostat può essere scritto, ma anche aggiornato con il comando setpointRaiseLower.

Esempio di utilizzo della funzione di aggiornamento per modificare il valore di un attributo

Questo esempio mostra come impostare esplicitamente il valore dell'attributo DoorLockTrait.WrongCodeEntryLimit.

Per impostare un valore dell'attributo, chiama la funzione update del tratto e passa una funzione di modificatore che imposta il nuovo valore. È buona norma prima verificare che la caratteristica supporti un attributo.

Ad esempio:

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

Inviare più comandi contemporaneamente

L'API di raggruppamento consente a un client di inviare più comandi per i dispositivi delle API Home in un unico payload. I comandi vengono raggruppati in un unico payload ed eseguiti in parallelo, in modo simile a come si potrebbe creare un'automazione dell'API Home utilizzando il nodo parallelo, come nell'esempio Apri le persiane prima dell'alba. Tuttavia, l'API di raggruppamento consente comportamenti più complessi e sofisticati rispetto all'API di automazione, ad esempio la possibilità di selezionare dinamicamente i dispositivi in fase di esecuzione in base a qualsiasi criterio.

I comandi in un batch possono avere come target più caratteristiche su più dispositivi, in più stanze e in più strutture.

L'invio di comandi in batch consente ai dispositivi di eseguire azioni contemporaneamente, cosa che non è possibile quando i comandi vengono inviati in sequenza in richieste separate. Il comportamento ottenuto utilizzando i comandi raggruppati consente allo sviluppatore di impostare lo stato di un gruppo di dispositivi in modo che corrisponda a uno stato aggregato predeterminato.

Utilizzare l'API di raggruppamento

Per richiamare i comandi tramite l'API Batching, sono necessari tre passaggi di base:

  1. Richiama il metodo Home.sendBatchedCommands().
  2. Nel corpo del blocco sendBatchedCommands(), specifica i comandi da includere nel batch.
  3. Controlla i risultati dei comandi inviati per verificare se sono andati a buon fine o meno.

Richiama il metodo sendBatchedCommands()

Chiama il metodo Home.sendBatchedCommands(). Dietro le quinte, questo metodo imposta un'espressione lambda in un contenuto batch speciale.

home.sendBatchedCommands() {

Specifica i comandi batch

All'interno del corpo del blocco sendBatchedCommands(), compila i comandi agrupabili. I comandi eseguibili in batch sono versioni "shadow" dei comandi dell'API Device esistenti che possono essere utilizzati in un contesto batch e sono denominati con il suffisso aggiuntivo Batchable. Ad esempio, il comando moveToLevel() del tratto LevelControl ha un corrispettivo denominato moveToLevelBatchable().

Esempio:

  val response1 = add(command1)

  val response2 = add(command2)

Il batch viene inviato automaticamente una volta aggiunti tutti i comandi al contesto del batch e l'esecuzione ha lasciato il contesto.

Le risposte vengono acquisite negli oggetti DeferredResponse<T>.

Le istanze DeferredResponse<T> possono essere raggruppate in un oggetto di qualsiasi tipo, ad esempio un Collection o una classe di dati che definisci. Qualsiasi tipo di oggetto scelto per assemblare le risposte viene restituito da sendBatchedCommands(). Ad esempio, il contesto batch può restituire due DeferredResponse in un Pair:

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

In alternativa, il contesto batch può restituire le istanze DeferredResponse in una classe di dati personalizzata:

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

Controlla ogni risposta

Al di fuori del blocco sendBatchedCommands(), controlla le risposte per determinare se il comando corrispondente è andato a buon fine o meno. Questo viene fatto chiamando DeferredResponse.getOrThrow(), che: - restituisce il risultato del comando eseguito, - oppure, se l'ambito del batch non è stato completato o il comando non è stato eseguito correttamente, genera un errore.

Devi controllare i risultati solo al di fuori dell'ambito lambda.sendBatchedCommands()

Esempio

Supponiamo che tu voglia creare un'app che utilizzi l'API di raggruppamento per configurare una scena "Buonanotte" che configuri tutti i dispositivi della casa per la notte, quando tutti sono a letto. Questa app dovrebbe spegnere le luci e chiudere le portiere anteriori e posteriori.

Ecco un modo per affrontare l'attività:

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