Android で自動化を構築する

Automation API には Android 用 Home API を介してアクセスできますが、エントリ ポイントはストラクチャを介しているため、使用する前にストラクチャに対する権限を付与する必要があります。

ストラクチャに対する権限が付与されたら、次のパッケージをアプリにインポートします。


import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.Id
import com.google.home.Structure

ストラクチャには、 HasAutomations 自動化固有の次のメソッドを持つインターフェースが含まれています。

API 説明
automations() ストラクチャに属するすべての自動化を一覧表示します。返されるのは、Home API を介して作成した自動化 のみです。
createAutomation(automation) ストラクチャの自動化インスタンスを作成します。
deleteAutomation(automationId) ID で自動化インスタンスを削除します。

自動化を作成する

Home のインスタンスを作成し、ユーザーから権限を受け取ったら、ストラクチャとデバイスを取得します。

val structure = homeManager.structures().list().single()
val device = homeManager.devices().get(Id("myDevice"))!!

次に、Automation DSL を使用して自動化のロジックを定義します。Home API では、自動化は Automation インターフェースで表されます。このインターフェースには、次のような一連のプロパティが含まれています。

  • 名前や説明などのメタデータ。
  • 自動化を実行できるかどうかを示すフラグ。
  • 自動化のロジックを含むノードのリスト。自動化グラフと呼ばれ、automationGraph プロパティで表されます。

automationGraphはデフォルトで SequentialFlow 型です。これは、順番に実行されるノードのリストを含むクラスです。各ノードは、開始条件、条件、アクションなど、自動化の要素を表します。

自動化に namedescription を割り当てます。

自動化を作成すると、isActive フラグはデフォルトで true に設定されるため、自動化を最初に無効にする場合を除き、このフラグを明示的に設定する必要はありません。その場合は、作成時にフラグを false に設定します。

DraftAutomation インターフェースは自動化の構築と作成に使用され、 Automation インターフェースは取得に使用されます。たとえば、別のデバイスがオンになったときにデバイスをオンにする自動化の Automation DSL は次のようになります。

import com.google.home.automation.Action
import com.google.home.automation.Automation
import com.google.home.automation.Condition
import com.google.home.automation.DraftAutomation
import com.google.home.automation.Equals
import com.google.home.automation.Node
import com.google.home.automation.SequentialFlow
import com.google.home.automation.Starter
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.matter.standard.OnOff
import com.google.home.Structure

...

val automation: DraftAutomation = automation {
  name = "MyFirstAutomation"
  description = "Turn on a device when another device is turned on."
  sequential {
    val starterNode = starter<_>(device1, OnOffLightDevice, trait=OnOff)
    condition() { expression = stateReaderNode.onOff equals true }
    action(device2, OnOffLightDevice) { command(OnOff.on()) }
  }
}

自動化 DSL を定義したら、 createAutomation() メソッドに渡して DraftAutomation インスタンスを作成します。

val createdAutomation = structure.createAutomation(automation)

ここから、execute()stop()update() など、自動化の他のすべての自動化メソッドを使用できます。

検証エラー

自動化の作成が検証に合格しなかった場合、警告またはエラー メッセージに問題に関する情報が表示されます。詳細については、 ValidationIssueType リファレンスをご覧ください。

コードの例

Android で自動化を設計するページで説明されている仮想の自動化の一部を実装するために使用できるコード例をいくつか示します。

シンプルな自動化

午前 8 時にブラインドを上げる自動化 は、次のように実装できます。

// get all the automation node candidates in the structure
val allCandidates = structure.allCandidates().first()
// determine whether a scheduled automation can be constructed
val isSchedulingSupported =
  allCandidates.any {
    it is EventCandidate &&
      it.eventFactory == Time.ScheduledTimeEvent &&
      it.unsupportedReasons.isEmpty()
  }
// get the blinds present in the structure
val blinds =
  allCandidates
    .filter {
      it is CommandCandidate &&
        it.commandDescriptor == WindowCoveringTrait.UpOrOpenCommand &&
        it.unsupportedReasons.isEmpty()
    }
    .map { it.entity }
    .filterIsInstance<HomeDevice>()
    .filter { it.has(WindowCoveringDevice) }
 if (isSchedulingSupported && blinds.isNotEmpty()) {
  // Proceed to create automation
  val automation: DraftAutomation = automation {
    name = "Day time open blinds"
    description = "Open all blinds at 8AM everyday"
    isActive = true
    sequential {
      // At 8:00am local time....
      val unused =
        starter(structure, Time.ScheduledTimeEvent) {
          parameter(Time.ScheduledTimeEvent.clockTime(LocalTime.of(8, 0, 0, 0)))
        }
        // ...open all the blinds
       parallel {
        for (blind in blinds) {
          action(blind, WindowCoveringDevice) { command(WindowCovering.upOrOpen()) }
        }
      }
    }
  }
   val createdAutomation = structure.createAutomation(automation)
} else if (!isSchedulingSupported) {
  // Cannot create automation.
  // Set up your address on the structure, then try again.
} else {
  // You don't have any WindowCoveringDevices.
  // Try again after adding some blinds to your structure.
}

複雑な自動化

動きが検出されたときにライトを点滅させる自動化は、次のように実装できます。

import com.google.home.Home
import com.google.home.HomeClient
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
import com.google.home.automation.action
import com.google.home.automation.automation
import com.google.home.automation.equals
import com.google.home.automation.parallel
import com.google.home.automation.starter
import com.google.home.google.AssistantBroadcast
import com.google.home.matter.standard.OnOff
import com.google.home.matter.standard.OnOff.Companion.toggle
import com.google.home.matter.standard.OnOffLightDevice
import java.time.Duration

// get all the automation node candidates in the structure
val allCandidates = structure.allCandidates().first()

// get the lights present in the structure
val availableLights = allCandidates.filter {
   it is CommandCandidate &&
   it.commandDescriptor == OnOffTrait.OnCommand
}.map { it.entity }
.filterIsInstance<HomeDevice>()
.filter {it.has(OnOffLightDevice) ||
         it.has(ColorTemperatureLightDevice) ||
         it.has(DimmableLightDevice) ||
         it.has(ExtendedColorLightDevice)}

val selectedLights = ... // user selects one or more lights from availableLights

automation {
isActive = true

sequential {
   // If the presence state changes...
   val starterNode = starter<_>(structure, AreaPresenceState)
   // ...and if the area is occupied...
   condition() {
      expression = starterNode.presenceState equals PresenceState.PresenceStateOccupied
   }
   // "blink" the light(s)
   parallel {
            for(light in selectedLights) {
            action(light, OnOffLightDevice) { command(OnOff.toggle()) }
            delayFor(Duration.ofSeconds(1))
            action(light, OnOffLightDevice) { command(OnOff.toggle()) }
            delayFor(Duration.ofSeconds(1))
            action(light, OnOffLightDevice) { command(OnOff.toggle()) }
            delayFor(Duration.ofSeconds(1))
            action(light, OnOffLightDevice) { command(OnOff.toggle())}
         }
      }
   }
}

エンティティ フィルタを使用してデバイスを動的に選択する

自動化を作成するときに、特定のデバイスを指定する必要はありません。 エンティティ フィルタと呼ばれる機能を使用すると、自動化でさまざまな条件に基づいて実行時にデバイスを選択できます。

たとえば、エンティティ フィルタを使用すると、自動化で次のものをターゲットにできます。

  • 特定のデバイスタイプのすべてのデバイス
  • 特定の部屋にあるすべてのデバイス
  • 特定の部屋にある特定のデバイスタイプのすべてのデバイス
  • オンになっているすべてのデバイス
  • 特定の部屋でオンになっているすべてのデバイス

エンティティ フィルタを使用するには:

  1. Structure または RoomatExecutionTime() を呼び出します。 これにより、TypedExpression<TypedEntity<StructureType>> が返されます。
  2. このオブジェクトで getDevicesOfType()を呼び出し、 DeviceTypeを渡します。

エンティティ フィルタは、開始条件、状態リーダー、アクションで使用できます。

たとえば、任意のオン/オフ ライトで開始条件から自動化をトリガーするには:

// If any light is turned on or off
val starter =
  starter(
    entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )

状態リーダーで、ストラクチャ内のすべてのライト(特にオン/オフ ライト)の OnOff 状態をキャプチャするには:

// Build a Map<Entity, OnOff>
val onOffStateOfAllLights =
  stateReader(
    entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )

特定の部屋のライトを取得して条件で使用するには:

val livingRoomLights =
  stateReader(
    entityExpression = livingRoom.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )
// Are any of the lights in the living room on?
condition { expression = livingRoomLights.values.any { it.onOff equals true } }

実行時:

シナリオ 結果
開始条件の条件を満たすデバイスがない。 自動化がトリガーされない。
状態リーダーの条件を満たすデバイスがない。 自動化は開始されますが、条件ノードに基づいて続行されます。
アクションの条件を満たすデバイスがない。 自動化は開始されますが、アクションは何も行いません。

次の例は、個々のライトがオフになるたびに、廊下のライトを除くすべてのライトをオフにする自動化です。

val unused = automation {
  sequential {
    // If any light is turned on or off
    val starter =
      starter(
        entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
        trait = OnOff,
      )
    condition {
      // Check to see if the triggering light was turned off
      expression = starter.onOff equals false
    }
    // Turn off all lights except the hall light
    action(
      entityExpression =
        structure.atExecutionTime().getDevicesOfType(OnOffLightDevice).filter {
          it notEquals entity(hallwayLight, OnOffLightDevice)
        }
    ) {
      command(OnOff.on())
    }
  }
}

自動化を実行する

execute() メソッドを使用して、作成した自動化を実行します。

createdAutomation.execute()

自動化に 手動開始条件がある場合、execute() その時点から自動化を開始し、 手動開始条件の前のすべてのノードを無視します。自動化に手動開始条件がない場合、実行は最初の開始条件ノードの次のノードから開始されます。

execute() オペレーションが失敗すると、HomeException がスローされることがあります。エラー 処理をご覧ください。

自動化を停止する

stop() メソッドを使用して、実行中の自動化を停止します。


createdAutomation.stop()

stop() オペレーションが失敗すると、HomeException がスローされることがあります。エラー 処理をご覧ください。

ストラクチャの自動化のリストを取得する

自動化はストラクチャ レベルで定義されます。ストラクチャの automations() で収集して、自動化のFlowにアクセスします。


import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
structure.automations().collect {
  println("Available automations:")
  for (automation in it) {
    println(String.format("%S %S", "$automation.id", "$automation.name"))
  }
}

または、ローカルの Collection に割り当てます。

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

var myAutomations: Collection<Automation> = emptyList()
myAutomations = structure.automations()

ID で自動化を取得する

自動化 ID で自動化を取得するには、ストラクチャで automations() メソッドを呼び出し、ID で照合します。

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().mapNotNull {
  it.firstOrNull
    { automation -> automation.id == Id("automation-id") }
  }.firstOrNull()

対応:

// Here's how the automation looks like in the get response.
// Here, it's represented as if calling a println(automation.toString())

Automation(
  name = "automation-name",
  description = "automation-description",
  isActive = true,
  id = Id("automation@automation-id"),
  automationGraph = SequentialFlow(
    nodes = [
      Starter(
        entity="device@test-device",
        type="home.matter.0000.types.0101",
        trait="OnOff@6789..."),
      Action(
        entity="device@test-device",
        type="home.matter.0000.types.0101",
        trait="OnOff@8765...",
        command="on")
    ]))

名前で自動化を取得する

Kotlin の filter() メソッドを使用して、API 呼び出しをさらに絞り込むことができます。名前で自動化を取得するには、ストラクチャの自動化を取得し、自動化名でフィルタします。

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().filter {
  it.name.equals("Sunset Blinds") }

デバイスのすべての自動化を取得する

特定のデバイスを参照するすべての自動化を取得するには、ネストされたフィルタを使用して、各自動化の automationGraph をスキャンします。

import android.util.Log
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
import com.google.home.automation.Action
import com.google.home.automation.Automation
import com.google.home.automation.Automation.automationGraph
import com.google.home.automation.Node
import com.google.home.automation.ParallelFlow
import com.google.home.automation.SelectFlow
import com.google.home.automation.SequentialFlow
import com.google.home.automation.Starter
import com.google.home.automation.StateReader

...

fun collectDescendants(node: Node): List<Node> {
  val d: MutableList<Node> = mutableListOf(node)

  val children: List<Node> =
    when (node) {
      is SequentialFlow -> node.nodes
      is ParallelFlow -> node.nodes
      is SelectFlow -> node.nodes
      else -> emptyList()
    }
  for (c in children) {
    d += collectDescendants(c)
  }
  return d
}

val myDeviceId = "device@452f78ce8-0143-84a-7e32-1d99ab54c83a"
val structure = homeManager.structures().list().single()
val automations =
  structure.automations().first().filter {
    automation: Automation ->
    collectDescendants(automation.automationGraph!!).any { node: Node ->
      when (node) {
        is Starter -> node.entity.id.id == myDeviceId
        is StateReader -> node.entity.id.id == myDeviceId
        is Action -> node.entity.id.id == myDeviceId
        else -> false
      }
    }
  }

自動化を更新する

自動化のメタデータを更新するには、 update() メソッドを呼び出し、メタデータを設定するラムダ式を渡します。

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().mapNotNull {
  it.firstOrNull
    { automation -> automation.id == Id("automation-id") }
  }.firstOrNull()
automation.update { this.name = "Flashing lights 2" }

The update() メソッドは、自動化グラフの完全な置き換えをサポートしていますが、グラフのノードごとの編集をサポートしていません。 ノード間の依存関係があるため、ノードごとの編集はエラーが発生しやすくなります。自動化のロジックを変更する場合は、新しいグラフを生成して既存のグラフを完全に置き換えます。

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: Automation = structure.automations().mapNotNull {
  it.firstOrNull
    { automation -> automation.id == Id("automation-id") }
  }.firstOrNull()
automation.update {
  this.automationGraph = sequential {
    val laundryWasherCompletionEvent =
      starter<_>(laundryWasher, LaundryWasherDevice, OperationCompletionEvent)
    condition {
      expression =
        laundryWasherCompletionEvent.completionErrorCode equals
          // UByte 0x00u means NoError
          0x00u
    }
    action(speaker, SpeakerDevice) { command(AssistantBroadcast.broadcast("laundry is done")) }
    }
  }
}

自動化を削除する

自動化を削除するには、ストラクチャの deleteAutomation() メソッドを使用します。自動化は ID を使用して削除する必要があります。

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().first()
structure.deleteAutomation(automation.id)

削除に失敗すると、HomeException がスローされることがあります。エラー 処理をご覧ください。

デバイスの削除が自動化に与える影響

ユーザーが自動化で使用されているデバイスを削除すると、削除されたデバイスは開始条件をトリガーできなくなり、自動化はそのデバイスから属性を読み取ったり、コマンドを発行したりできなくなります。たとえば、ユーザーが家から OccupancySensorDevice を削除し、自動化に the OccupancySensorDeviceに依存する開始条件がある場合、その開始条件は自動化をアクティブにできなくなります。