確認特徵是否支援指令
你也可以檢查特徵指令的支援情形。此外,您也可以使用特徵層級的 supports
函式,檢查特定裝置是否支援指令。
舉例來說,如要檢查裝置是否支援 On/Off 特徵的 toggle
指令:
// 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") }
將指令傳送至裝置
傳送指令與從特徵讀取狀態屬性類似。如要開啟或關閉裝置,請使用 OnOff
特徵的 Toggle 指令,該指令在 Google Home 生態系統資料模型中定義為 toggle()
。這個方法會將 onOff
變更為 false
(如果目前為 true
),或變更為 true
(如果目前為 false
):
// Calling a command on a trait. try { onOffTrait.toggle() } catch (e: HomeException) { // Code for handling the exception }
所有特徵指令都是 suspend
函式,只有在 API 傳回回應時 (例如確認裝置狀態已變更),才會完成。如果執行流程偵測到問題,指令可能會傳回例外狀況。身為開發人員,您應使用 try-catch
區塊妥善處理這些例外狀況,並在錯誤可採取行動時,向使用者提供詳細資訊。未處理的例外狀況會停止應用程式執行階段,並可能導致應用程式當機。
或者,您也可以使用 off()
或 on()
指令明確設定狀態:
onOffTrait.off() onOffTrait.on()
傳送指令來變更狀態後,您可以在指令完成時讀取狀態,如「讀取裝置狀態」一節所述,以便在應用程式中處理狀態。或者,您也可以使用「觀察狀態」一節所述的流程,這是建議採用的方法。
傳送含參數的指令
部分指令可能會使用參數,例如 OnOff
或 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(), )
部分指令有選用引數,這些引數會放在必要引數之後。
舉例來說,FanControl
特徵的 step
指令有兩個選用引數:
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 }
確認特徵是否支援屬性
部分裝置可能支援 Matter 特徵,但不支援特定屬性。舉例來說,對應至 Matter 的 Cloud-to-cloud 裝置可能不支援每個 Matter 屬性。如要處理這類情況,請使用特徵層級的 supports
函式和特徵的 Attribute
列舉,檢查特定裝置是否支援該屬性。
舉例來說,如要檢查裝置是否支援 On/Off 特徵的 onOff
屬性:
// 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!") }
部分屬性在 Matter 規格或 Cloud-to-cloud smart home 架構中可為空值。對於這些屬性,您可以同時使用 supports
和 isNullable
,判斷屬性傳回的 null 是因為裝置未回報該值,還是屬性的值實際上為 null
:
// 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!") }
更新特徵屬性
如要變更特定屬性的值,但特徵的任何指令都無法執行這項操作,該屬性可能支援明確設定值。
屬性的值是否可以變更,取決於下列兩項因素:
- 屬性是否可寫入?
- 傳送特徵指令時,屬性的值是否會隨之變更?
如要瞭解這項資訊,請參閱特徵及其屬性的參考說明文件。
因此,決定屬性值變更方式的屬性組合如下:
唯讀,不受其他指令影響。也就是說,屬性的值不會變更。舉例來說,
Switch
特徵的currentPosition
屬性。唯讀,且受其他指令影響。也就是說,只有在傳送指令後,屬性的值才會變更。舉例來說,
LevelControl
Matter 特徵的currentLevel
屬性為唯讀,但其值可透過moveToLevel
等指令變動。可寫入,且不受其他指令影響。也就是說,您可以使用特徵的
update
函式直接變更屬性的值,但沒有任何指令會影響屬性的值。舉例來說,DoorLock
特徵的WrongCodeEntryLimit
屬性。可寫入,且受其他指令影響。也就是說,您可以使用特徵的
update
函式直接變更屬性的值,而屬性的值可能會因傳送指令而變更。舉例來說,FanControlTrait
的speedSetting
屬性可以直接寫入,但也可以使用step
指令變更。
使用更新函式變更屬性值的範例
這個範例說明如何明確設定 DoorLockTrait.WrongCodeEntryLimit
屬性的值。
如要設定屬性值,請呼叫特徵的 update
函式,並傳遞設定新值的突變子函式。
建議您先確認特徵支援屬性。
例如:
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) } }
一次傳送多個指令
透過 Batching API,用戶端可以在單一酬載中傳送多個 Home API 裝置指令。這些指令會批次處理成單一酬載,並以平行方式執行,類似於使用平行節點建構 Home API 自動化動作,例如「在日出前打開百葉窗」。不過,Batching API 比 Automation API 更複雜精細,例如可根據任何條件,在執行階段動態選取裝置。
一個批次中的指令可以針對多個裝置、多個房間和多個結構中的多個特徵。
批次傳送指令可讓裝置同時執行動作,如果透過個別要求依序傳送指令,則無法達到此效果。開發人員可使用批次指令,將一組裝置的狀態設為符合預先決定的匯總狀態。
使用 Batching API
透過 Batching API 叫用指令的基本步驟有三項:
- 叫用
Home.sendBatchedCommands()
方法。 - 在
sendBatchedCommands()
區塊的主體中,指定要納入批次的指令。 - 檢查傳送的指令結果,確認指令是否成功。
叫用 sendBatchedCommands() 方法
呼叫 Home.sendBatchedCommands()
方法。在幕後,這個方法會在特殊批次環境中設定 lambda 運算式。
home.sendBatchedCommands() {
指定批次指令
在 sendBatchedCommands()
區塊的內文中,填入可批次處理的指令。可批次處理的指令是現有裝置 API 指令的「陰影」版本,可用於批次處理環境,並以新增的 Batchable
後置字串命名。舉例來說,LevelControl
特徵的 moveToLevel()
指令有對應的 moveToLevelBatchable()
指令。
範例:
val response1 = add(command1)
val response2 = add(command2)
所有指令都加入批次內容,且執行作業已離開內容後,系統就會自動傳送批次。
回應會擷取到 DeferredResponse<T>
物件中。
DeferredResponse<T>
例項可以收集到任何類型的物件中,例如 Collection
或您定義的資料類別。無論您選擇組裝回應的物件類型為何,sendBatchedCommands()
都會傳回該類型。舉例來說,批次內容可以在 Pair
中傳回兩個 DeferredResponse
例項:
val (response1, response2) = homeClient.sendBatchedComamnds {
val response1 = add(someCommandBatched(...))
val response2 = add(someOtherCommandBatched(...))
Pair(response1, response2)
}
或者,批次內容可以在自訂資料類別中傳回 DeferredResponse
例項:
// Custom data class
data class SpecialResponseHolder(
val response1: DeferredResponse<String>,
val response2: DeferredResponse<Int>,
val other: OtherResponses
)
data class OtherResponses(...)
檢查每則回覆
在 sendBatchedCommands()
區塊外,檢查回應來判斷對應的指令是否成功或失敗。方法是呼叫 DeferredResponse.getOrThrow()
,該方法會:
- 傳回已執行指令的結果。
- 如果批次範圍尚未完成或指令執行失敗,則會擲回錯誤。
您應該只在 sendBatchedCommands()
lambda 範圍外檢查結果。
範例
假設您想建構一個應用程式,使用 Batching API 設定「晚安」場景,在所有人都睡著時,將住家中的所有裝置設為夜間模式。這個應用程式應會關燈,並鎖上前後門。
以下是其中一種做法:
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()
}