Controllare se una caratteristica supporta un comando
È possibile controllare il supporto anche per un comando di tratto. Utilizza anche la funzione supports a livello di tratto per verificare se un comando è supportato per un determinato dispositivo.
Ad esempio, per verificare il supporto di un dispositivo per il comando
toggle
della caratteristica 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 di 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 modifica onOff in false se è true, oppure in true se è false:
// Calling a command on a trait. try { onOffTrait.toggle() } catch (e: HomeException) { // Code for handling the exception }
Tutti i comandi trait 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. Come sviluppatore, dovresti utilizzare un blocco try-catch per gestire correttamente queste eccezioni e fornire agli utenti informazioni dettagliate sui casi in cui gli errori sono risolvibili. Le eccezioni non gestite interromperanno il runtime dell'app e potrebbero causare arresti anomali dell'app.
In alternativa, utilizza i comandi off() o on() per impostare esplicitamente lo stato:
onOffTrait.off() onOffTrait.on()
Dopo aver inviato un comando per modificare lo stato, una volta completato puoi leggere lo stato come descritto in Leggere lo stato di un dispositivo per gestirlo nella tua app. In alternativa, utilizza i flussi come descritto in Osservare lo stato, che è il metodo preferito.
Invia un comando con parametri
Alcuni comandi potrebbero utilizzare parametri, come quelli sui tratti OnOff o LevelControl:
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(), )
Alcuni comandi hanno argomenti facoltativi, che seguono quelli obbligatori.
Ad esempio, il comando step per la caratteristica 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 }
Controlla se un tratto supporta un attributo
Alcuni dispositivi potrebbero supportare una caratteristica 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, utilizzare la funzione supports a livello di tratto e l'enum Attribute del tratto per verificare se l'attributo è supportato per un dispositivo specifico.
Ad esempio, per verificare il supporto di un dispositivo per l'attributo
onOff
della caratteristica 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 annullabili 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 segnala quel valore 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 del tratto
Se vuoi modificare il valore di un determinato attributo e nessuno dei comandi della caratteristica 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 di tratto?
Queste informazioni sono disponibili nella documentazione di riferimento per i tratti e i relativi attributi.
Pertanto, le combinazioni di proprietà che determinano come il valore di un attributo potrebbe essere modificato sono:
Di sola lettura e non influenzato da altri comandi. Ciò significa che il valore dell'attributo non cambia. Ad esempio, l'attributo
currentPositiondel trattoSwitch.Di sola lettura e influenzato da altri comandi. Ciò significa che l'unico modo in cui il valore dell'attributo può cambiare è a seguito dell'invio di un comando. Ad esempio, l'attributo
currentLeveldel trattoLevelControlMatter è di sola lettura, ma il suo valore può essere modificato da comandi comemoveToLevel.Scrivibile e non interessato da altri comandi. Ciò significa che puoi modificare direttamente il valore dell'attributo utilizzando la funzione
updatedel tratto, ma non esistono comandi che influiranno sul valore dell'attributo. Ad esempio, l'attributoWrongCodeEntryLimitdel trattoDoorLock.Scrivibile e influenzato da altri comandi. Ciò significa che puoi modificare direttamente il valore dell'attributo utilizzando la funzione
updatedel tratto e il valore dell'attributo può cambiare in seguito all'invio di un comando. Ad esempio, l'attributospeedSettingdiFanControlTraitpuò essere scritto direttamente, ma è anche modificabile utilizzando il comandostep.
Esempio di utilizzo della funzione di aggiornamento per modificare il valore di un attributo
Questo esempio mostra come impostare in modo esplicito il valore dell'attributo
DoorLockTrait.WrongCodeEntryLimit.
Per impostare un valore dell'attributo, chiama la funzione update del tratto e trasmetti una funzione mutatore che imposta il nuovo valore.
È consigliabile prima
verificare che il tratto supporti un attributo.
Ad esempio:
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) } }
Inviare più comandi contemporaneamente
L'API Batching consente a un client di inviare più comandi API Home per i dispositivi 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, ad esempio l'esempio Apri le tapparelle prima dell'alba. Tuttavia, l'API Batching consente comportamenti più complessi e sofisticati rispetto all'API Automation, ad esempio la possibilità di selezionare dinamicamente i dispositivi in fase di runtime in base a qualsiasi criterio.
I comandi di un batch possono avere come target più caratteristiche su più dispositivi, in più stanze, in più strutture.
L'invio di comandi in batch consente ai dispositivi di eseguire azioni contemporaneamente, cosa non possibile quando i comandi vengono inviati in sequenza in richieste separate. Il comportamento ottenuto utilizzando i comandi batch consente allo sviluppatore di impostare lo stato di un gruppo di dispositivi in modo che corrisponda a uno stato aggregato predeterminato.
Utilizzare l'API Batching
Per richiamare i comandi tramite l'API Batching sono necessari tre passaggi fondamentali:
- Richiama il metodo
Home.sendBatchedCommands(). - All'interno del corpo del blocco
sendBatchedCommands(), specifica i comandi da includere nel batch. - Controlla i risultati dei comandi inviati per verificare se sono stati eseguiti correttamente o meno.
Richiama il metodo sendBatchedCommands()
Chiama il metodo
Home.sendBatchedCommands(). Dietro le quinte, questo metodo configura un'espressione lambda in un contesto batch speciale.
home.sendBatchedCommands() {
Specifica i comandi batch
All'interno del corpo del blocco sendBatchedCommands(), inserisci i comandi batchable. I comandi batch sono versioni "ombra" dei comandi dell'API Device esistenti che possono essere utilizzati in un contesto batch e sono denominati con il suffisso aggiunto Batchable. Ad esempio, il comando
LevelControl
dell'attributo
moveToLevel()
ha una controparte denominata
moveToLevelBatchable().
Esempio:
val response1 = add(command1)
val response2 = add(command2)
Il batch viene inviato automaticamente una volta che tutti i comandi sono stati aggiunti al contesto batch e l'esecuzione è uscita dal contesto.
Le risposte vengono acquisite negli oggetti DeferredResponse<T>.
Le istanze DeferredResponse<T> possono essere raccolte in un oggetto di qualsiasi tipo, ad esempio Collection, o in una classe di dati definita dall'utente. Qualunque sia il tipo di oggetto che scegli per assemblare le risposte, questo è ciò che viene restituito da sendBatchedCommands(). Ad esempio, il contesto batch può restituire due
istanze 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(...)
Controllare ogni risposta
Al di fuori del blocco sendBatchedCommands(), controlla le risposte per determinare
se il comando corrispondente è riuscito o meno. Questa operazione viene eseguita chiamando
DeferredResponse.getOrThrow(), che:
- restituisce il risultato del comando eseguito,
- oppure, se l'ambito batch non è stato completato o il comando non è
andato a buon fine, genera un errore.
Devi controllare i risultati solo al di fuori dell'ambito lambda di sendBatchedCommands().
Esempio
Supponiamo che tu voglia creare un'app che utilizzi l'API Batching per configurare una scena "Buonanotte" che configuri tutti i dispositivi della casa per la notte, quando tutti dormono. Questa app dovrebbe spegnere le luci e chiudere le porte anteriore e posteriore.
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()
}