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 特性の Toggle コマンドを使用します。これは、Google Home エコシステムのデータ モデルで toggle() として定義されています。このメソッドは、onOfftrue の場合は false に、false の場合は 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 などのパラメータを使用する場合があります。

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

一部のコマンドには、必須の引数の後に省略可能な引数があります。

たとえば、step コマンド(FanControl トレイト )には、次の 2 つの省略可能な引数があります。

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 特性をサポートしていても、特定の属性をサポートしていない場合があります。たとえば、Cloud-to-cloud デバイスが Matter にマッピングされている場合、すべての 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 スキーマで null 許容です。これらの属性については、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!")
}

特性属性を更新する

特定の属性の値を変更したいが、特性のコマンドでは変更できない場合、その属性は値を明示的に設定できる可能性があります。

属性の値を変更できるかどうかは、次の 2 つの要因によって異なります。

  • 属性は書き込み可能ですか?
  • 特性コマンドの送信の副作用として、属性の値が変更されることはありますか?

特性とその属性のリファレンス ドキュメントには、この情報が記載されています。

したがって、属性の値がどのように変更されるかを決定するプロパティの組み合わせは次のとおりです。

update 関数を使用して属性の値を変更する例

この例では、 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 デバイス コマンドを 1 つのペイロードで送信できます。コマンドは 1 つのペイロードにバッチ処理され、 並行して実行されます。これは、Home API 自動化並列ノードを使用して構築する方法と似ています。 日の出前にブラインドを開ける の例などです。ただし、Batching API では、Automation API よりも複雑で高度な動作が可能です。たとえば、任意の条件に基づいて実行時にデバイスを動的に選択できます。

1 つのバッチ内のコマンドは、複数のデバイス、複数の部屋、複数の構造にまたがる複数の特性をターゲットにできます。

コマンドをバッチで送信すると、デバイスは同時にアクションを実行できます。これは、個別のリクエストでコマンドを順番に送信する場合では実現できません。バッチコマンドを使用して実現される動作により、デベロッパーはデバイス グループの状態を、事前に決定された集約状態と一致するように設定できます。

Batching API を使用する

Batching API を介してコマンドを呼び出すには、次の 3 つの基本的な手順があります。

  1. Home.sendBatchedCommands() メソッドを呼び出します。
  2. sendBatchedCommands() ブロックの本文内で、バッチに含めるコマンドを指定します。
  3. 送信されたコマンドの結果を確認して、成功したか失敗したかを確認します。

sendBatchedCommands() メソッドを呼び出す

Home.sendBatchedCommands() メソッドを呼び出します。このメソッドは、特別なバッチ コンテキストでラムダ式を設定します。

home.sendBatchedCommands() {

バッチコマンドを指定する

sendBatchedCommands() ブロックの本文内で、バッチ処理可能なコマンド を入力します。バッチ処理可能なコマンドは、バッチ コンテキストで使用できる既存の Device API コマンドの「シャドウ」バージョンであり、接尾辞 Batchable が付いています。たとえば、 LevelControl 特性の moveToLevel() コマンドには、 moveToLevelBatchable()という名前の対応するコマンドがあります。

例:

  val response1 = add(command1)

  val response2 = add(command2)

すべてのコマンドが バッチ コンテキストに追加され、実行がコンテキストから離れると、バッチは自動的に送信されます。

レスポンスは DeferredResponse<T> オブジェクトにキャプチャされます。

DeferredResponse<T> インスタンスは、 Collection などの任意の型のオブジェクトや、定義したデータクラスに収集できます。レスポンスを組み立てるオブジェクトの型は、 sendBatchedCommands() によって返されます。たとえば、バッチ コンテキストは、 2 つの DeferredResponse インスタンスを Pair で返すことができます。

  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() ラムダ スコープの外部 のみにしてください。

Batching API を使用して、全員が寝ている夜間に家のすべてのデバイスを設定する「おやすみ」シーンを設定するアプリを作成するとします。このアプリは、照明を消し、玄関と裏口のドアをロックする必要があります。

このタスクに取り組む方法の 1 つは次のとおりです。

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