在 Android 裝置上控制裝置

檢查特徵是否支援指令

您也可以檢查是否支援特徵指令。請一併使用特徵層級的 supports 函式,檢查特定裝置是否支援指令。

舉例來說,如要檢查裝置是否支援開/關特徵的 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 特徵的切換指令,在 Google Home 生態系統資料模型中定義為 toggle()。如果 onOfftrue,這個方法會將其變更為 false;如果 onOfffalse,則會變更為 true

// 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 特徵中的參數:

// 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(),
)

部分指令含有選用引數,會在必要引數之後出現。

舉例來說,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 列舉,檢查特定裝置是否支援該屬性。

舉例來說,如要檢查裝置是否支援開/關特徵的 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 架構中,部分屬性可為空值。針對這些屬性,您可以使用 isNullablesupports,判斷屬性傳回的 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 函式直接變更屬性值,而屬性值也會隨著傳送指令而變更。舉例來說,Thermostat 特徵的 occupiedCoolingSetpoint 屬性可以寫入,但也可以透過 setpointRaiseLower 指令進行更新。

使用更新函式變更屬性值的範例

這個範例說明如何明確設定 DoorLockTrait.WrongCodeEntryLimit 屬性的值。

如要設定屬性值,請呼叫特徵的 update 函式,並將設定新值的修飾符函式傳遞給該函式。建議您先確認特徵是否支援屬性

例如:

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

一次傳送多個指令

批次 API 可讓用戶端在單一酬載中傳送多個 Google Home API 裝置指令。指令會匯入單一酬載,並以平行方式執行,類似於使用平行節點建立 Google Home API 自動化動作的方式,例如在日出前開啟百葉窗的範例。不過,Batching API 可執行比 Automation API 更複雜、更精密的行為,例如在執行階段根據任何條件動態選取裝置。

一批指令可指定多個裝置、多個房間和多個結構體中的多個特徵。

以批次傳送指令可讓裝置同時執行動作,但如果指令是透過個別要求依序傳送,就無法達到這個效果。使用批次指令所達成的行為,可讓開發人員設定一組裝置的狀態,以符合預先定義的匯總狀態。

使用 Batching API

透過 Batching API 叫用指令時,需要執行三個基本步驟:

  1. 叫用 Home.sendBatchedCommands() 方法。
  2. sendBatchedCommands() 區塊的內文中,指定要納入批次的命令。
  3. 檢查傳送指令的結果,瞭解指令是否成功或失敗。

叫用 sendBatchedCommands() 方法

呼叫 Home.sendBatchedCommands() 方法。在幕後,這個方法會在特殊批次內容中設定 lambda 運算式。

home.sendBatchedCommands() {

指定批次指令

sendBatchedCommands() 區塊的內文中,填入可批次處理的指令。可批次處理的指令是現有 Device 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()
}