Controlla se un tratto supporta un comando
È possibile verificare il supporto anche per un comando di un 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 di stato da un tratto. Per
accendere o spegnere il dispositivo, utilizza il
comando Toggle del tratto 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 sui 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 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 Leggi lo stato di un dispositivo per gestirlo nella tua app. In alternativa, utilizza i flussi come descritto in Osserva lo stato, che è il metodo preferito.
Invia un comando con parametri
Alcuni comandi possono utilizzare parametri, come quelli dei 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 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 }
Controlla 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 possono essere nulli nella specifica Matter o nello schema smart home Cloud-to-cloud. 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 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 suo 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 del tratto?
La documentazione di riferimento per i tratti e i relativi attributi fornisce queste informazioni.
Pertanto, le combinazioni di proprietà che determinano il modo in cui il valore di un attributo potrebbe essere modificato sono:
Sola lettura e non interessato da altri comandi. Ciò significa che il valore dell'attributo non cambia. Ad esempio, l'attributo
currentPositiondel trattoSwitch.Sola lettura e interessato da altri comandi. Ciò significa che l'unico modo in cui il valore dell'attributo può cambiare è l'invio di un comando. Ad esempio, l'attributo
currentLeveldella caratteristicaLevelControlMatter è 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 influiscono 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 esplicitamente il valore dell'attributo DoorLockTrait.WrongCodeEntryLimit.
Per impostare il valore dell'attributo, chiama la funzione update del trait e passale una funzione mutatore che imposta il nuovo valore.
È buona norma verificare prima 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) } }
Invia più comandi contemporaneamente
L'API Batching consente a un client di inviare più comandi dei 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 tende prima dell'alba. Tuttavia, l'API Batching consente comportamenti più complessi e sofisticati rispetto all'API Automation, come 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ù tratti su più dispositivi, in più stanze e in più strutture.
L'invio di comandi in un 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.
Utilizza l'API Batching
L'invocazione dei comandi tramite l'API Batching prevede tre passaggi di base:
- 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 vedere se sono stati eseguiti o meno.
Invia comandi in batch
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(), compila i comandi raggruppabili. I comandi raggruppabili 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 aggiunto Batchable. Ad esempio, il comando moveToLevel() del tratto LevelControl 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 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 definita da te. Qualsiasi tipo di oggetto che scegli per assemblare le risposte è quello 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(...)
Controlla ogni risposta
All'esterno del blocco sendBatchedCommands(), controlla le risposte per determinare se il comando corrispondente è riuscito o meno. Per farlo, chiama DeferredResponse.getOrThrow(), che:
- restituisce il risultato del comando eseguito
- oppure, se l'ambito del batch non è stato completato o il comando non è andato a buon fine, genera un errore.
Dovresti controllare i risultati solo all'esterno dell'ambito lambda sendBatchedCommands().
Esempio
Supponiamo che tu voglia creare un'app che utilizzi l'API Batching per configurare una scena "Buonanotte" che imposti tutti i dispositivi della casa per la notte, quando tutti dormono. Questa app dovrebbe spegnere le luci e chiudere le porte d'ingresso e sul retro.
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()
}
Controlla un gruppo di dispositivi
Oltre al controllo granulare su ogni membro di un gruppo eseguendo l'iterazione
sui dispositivi membri,
puoi controllare un
DeviceGroup come singola
entità. DeviceGroup implementa l'interfaccia PartsProvider, che espone le parti DeviceType supportate dai dispositivi del gruppo:
group.parts()restituisce unFlow<Set<DeviceType>>di tutti i tipi di dispositivi presenti nel gruppo.group.part()egroup.partOrNull()restituiscono un flusso per un tipo di dispositivo specifico, ad esempioOnOffLightDeviceoDimmableLightDevice.
Quando richiami un comando di un tratto su un tipo di dispositivo che fa parte di un DeviceGroup, l'SDK inoltra il comando a tutti i dispositivi membri associati nel gruppo che supportano questa funzionalità, indipendentemente dalla connettività o dal trasporto sottostanti (ad esempio Matter o Cloud-to-cloud).
Attributi dei tratti delle parti del gruppo e gestione degli errori
Il targeting di una parte del gruppo semplifica i casi comuni in cui vuoi inviare un comando idempotente (ad esempio accendere o spegnere le luci o impostare un livello di luminosità) a un gruppo. Quando utilizzi i tratti e i comandi su una parte DeviceGroup, tieni presente i seguenti comportamenti:
- Gli attributi di stato dinamici (come l'attributo
onOffdel trattoOnOffo l'attributocurrentLeveldel trattoLevelControl) in genere non vengono compilati nel tratto di una parte del gruppo perché i dispositivi membri di un gruppo possono trovarsi in stati diversi che non possono essere combinati in un unico valore. Tuttavia, i comandi supportati (controllati consupports(), ad esempioLevelControl.Command.MoveToLevelWithOnOffsu una luce dimmerabile) e alcuni attributi delle funzionalità (ad esempiocolorTempPhysicalMinMiredsecolorTempPhysicalMaxMiredssuColorControlper le luci colorate) vengono compilati in modo che l'app possa configurare i controlli UI in modo appropriato. - Non tutti i comandi relativi ai tratti dei membri del gruppo sono disponibili su una parte del gruppo. In particolare, i comandi che restituiscono valori non sono disponibili.
- Un comando su una parte del gruppo non restituisce risultati individuali per ogni comando del dispositivo membro sottostante. Per esaminare i risultati dei singoli comandi o chiamare comandi che restituiscono valori, esegui l'iterazione direttamente sui dispositivi membri del gruppo.
- I comandi inviati a una parte di un
DeviceGroupvengono completati una volta terminato l'intero fanout tra i dispositivi membri e dopo aver raccolto tutti i successi o i fallimenti dei comandi sottostanti. Un comando non va a buon fine (genera un erroreHomeException) solo se non va a buon fine per ogni dispositivo membro associato o se tutti i dispositivi di destinazione non sono disponibili, generando un singolo errore del comando corrispondente alla sintassi di chiamata del singolo comando. Se almeno un dispositivo membro esegue il comando, il comando di gruppo viene completato senza generare un'eccezione.
Invia comandi a una parte del gruppo
Per accendere tutte le luci OnOffLightDevice di un gruppo di dispositivi:
val lightPart = group.partOrNull(OnOffLightDevice).firstOrNull()
val onOffTrait = lightPart?.trait(OnOff)
if (onOffTrait?.supports(OnOff.Command.On) == true) {
onOffTrait.on()
}
Puoi anche ispezionare tutti i tipi di dispositivi presenti in un gruppo utilizzando group.parts()
per scegliere come target un tratto su più tipi di dispositivi correlati (ad esempio, disattivando
qualsiasi tipo di dispositivo di illuminazione nel gruppo che supporta
OnOff):
val lightTypes = listOf(
OnOffLightDevice,
DimmableLightDevice,
ColorTemperatureLightDevice,
ExtendedColorLightDevice,
)
val parts = group.parts().first()
val onOffTrait = lightTypes.firstNotNullOfOrNull { type ->
parts.firstOrNull { it.factory == type }
}?.trait(OnOff)
onOffTrait?.off()
Puoi verificare il supporto dei comandi o leggere i limiti delle funzionalità su una parte del gruppo prima di inviare un comando (ad esempio, controllando MoveToLevelWithOnOff su una parte di luce dimmerabile o leggendo colorTempPhysicalMinMireds e colorTempPhysicalMaxMireds su una parte di luce con temperatura di colore):
val dimmableLight = group.partOrNull(DimmableLightDevice).firstOrNull()
val levelControl = dimmableLight?.trait(LevelControl)
if (
levelControl?.supports(
LevelControl.Command.MoveToLevelWithOnOff
) == true
) {
levelControl.moveToLevelWithOnOff(
level = 127u,
transitionTime = null,
optionsMask = LevelControlTrait.OptionsBitmap(),
optionsOverride = LevelControlTrait.OptionsBitmap(),
)
}
val colorLight = group.partOrNull(ColorTemperatureLightDevice).firstOrNull()
val colorControl = colorLight?.trait(ColorControl)
val minMireds = colorControl?.colorTempPhysicalMinMireds
val maxMireds = colorControl?.colorTempPhysicalMaxMireds
if (colorControl != null && minMireds != null && maxMireds != null) {
val midMireds = ((minMireds.toInt() + maxMireds.toInt()) / 2).toUShort()
colorControl.moveToColorTemperature(
colorTemperatureMireds = midMireds,
transitionTime = 0u,
optionsMask = ColorControlTrait.OptionsBitmap(),
optionsOverride = ColorControlTrait.OptionsBitmap(),
)
}