在 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 屬性。若要處理此類情況,請使用 trait 層級的 supports 函數和 trait 的 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 架構中可為空值。對於這些屬性,您可以使用 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!")
}

更新特徵屬性

如要變更特定屬性的值,但特徵的任何指令都無法執行這項操作,該屬性可能支援明確設定值。

屬性的值是否可變更取決於兩項因素:

  • 屬性是否可寫入?
  • 傳送特徵指令時,屬性的值是否會隨之變更?

如要瞭解這項資訊,請參閱特徵及其屬性的參考說明文件。

因此,決定屬性值變更方式的屬性組合如下:

使用更新函數更改屬性值的範例

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

若要設定屬性值,請呼叫 trait 的 update 函數 並向其傳遞一個設定新值的 mutator 函數。 首先,這是一個很好的做法。驗證該特性是否支援某個屬性

例如:

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

一次發送多個命令

批次處理 API 可讓用戶端在單一酬載中傳送多個 Home API 裝置指令。這些指令被批次處理成一個單獨的有效負載並並行執行,類似於使用 並行節點 建構 Home API 自動化 的方式,例如 日出前打開百葉窗 範例。但是,批次 API 允許比自動化 API 更複雜、更精細的行為,例如能夠在運行時根據任何標準動態選擇設備。

一批指令可以針對多個設備、多個房間、多個建築物中的多個特性進行操作。

批次發送命令可以讓設備同時執行操作,而當命令在單獨的請求中按順序發送時,這是不可能實現的。使用批次命令實現的行為允許開發人員將一組設備的狀態設定為與預定的聚合狀態相符。

使用批次 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 作用域。

範例

假設你想開發一個使用批量 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()
}