在 Android 裝置上控制裝置

確認特徵是否支援指令

你也可以檢查特徵指令的支援情形。此外,您也可以使用特徵層級的 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()

傳送指令來變更狀態後,您可以在指令完成時讀取狀態,如「讀取裝置狀態」一節所述,以便在應用程式中處理狀態。或者,您也可以使用「觀察狀態」一節所述的流程,這是建議採用的方法。

傳送含參數的指令

部分指令可能會使用參數,例如 OnOffLevelControl 特徵中的參數:

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 特徵,但不支援特定屬性。舉例來說,對應至 MatterCloud-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 架構中可為空值。對於這些屬性,您可以同時使用 supportsisNullable,判斷屬性傳回的 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 函式直接變更屬性的值,而屬性的值可能會因傳送指令而變更。舉例來說,FanControlTraitspeedSetting 屬性可以直接寫入,但也可以使用 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 叫用指令的基本步驟有三項:

  1. 叫用 Home.sendBatchedCommands() 方法。
  2. sendBatchedCommands() 區塊的主體中,指定要納入批次的指令。
  3. 檢查傳送的指令結果,確認指令是否成功。

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