Android のデバイスとデバイスのメタデータにアクセスする

デバイス API には、Android 向け Google Home API を介してアクセスできます。これらのパッケージをアプリにインポートします。

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

Device API で特定のデバイスタイプまたはトレイトを使用するには、個別にインポートする必要があります。

たとえば、Matter のオン/オフ トレイトとオン/オフ プラグイン ユニット デバイスタイプを使用するには、次のパッケージをアプリにインポートします。

import com.google.home.matter.standard.OnOff
import com.google.home.matter.standard.OnOffPluginUnitDevice

詳細については、Android のデータモデルをご覧ください。

エラー処理

Home API のメソッドは HomeException をスローする可能性があるため、try-catch ブロックを使用してすべての呼び出しで HomeException をキャッチすることをおすすめします。

HomeException を処理する際は、 error.code フィールドと error.message フィールドをチェックして、問題の内容を確認します。サブエラーコードが存在する場合もあるため、 getSubErrorCodes() メソッドを呼び出して結果を確認してください。

処理されない例外があると、アプリがクラッシュします。

詳細については、エラー処理をご覧ください。

例については、デバイスにコマンドを送信するをご覧ください。

通話のサンプル

デバイスのリストを取得する

Structure インスタンスへの参照を取得したら、devices() を呼び出すことで、そのストラクチャからアクセス可能なデバイスの Flow が返されます。

// Get a flow of all devices accessible to the user
val allDevicesFlow: HomeObjectsFlow<HomeDevice> = home.devices()

// Calling list() on a HomeObjectsFlow returns the first Set of elements.
val allDevices: Set<HomeDevice> = allDevicesFlow.list()

そこから、各デバイスの状態にアクセスして、デバイスにコマンドを送信できます。

Google Home API のリリース 1.8 では、devices() メソッドの enableMultipartDevices パラメータを true に設定することで、API が各マルチパート デバイスを単一のデバイスとして表すようにできます。詳しくは、Android のマルチパート デバイスをご覧ください。

デバイスの状態を読み取る

デバイスのオン/オフ トレイトから OnOff 属性を確認する例を見てみましょう。このトレイトが OnOff として識別される Google Home API トレイト データモデルを使用すると、デバイスタイプの standardTraits クラスを介してトレイトデータを取得できます。

// Assuming we have a device.
val deviceFlow = home.devices().itemFlow(myDeviceId)

val device = deviceFlow.first()

// Get a flow of a standard trait on the type. distinctUntilChanged() is needed to only trigger
// on the specific trait changes and not the whole type.
val onOffTraitFlow: Flow<OnOff?> =
  device.type(DimmableLightDevice).map { it.standardTraits.onOff }.distinctUntilChanged()

val onOffTrait: OnOff = onOffTraitFlow.first()!!

Kotlin の flow 関数の詳細については、distinctUntilChanged をご覧ください。

特性サブスクリプションの状態を無効にする

TraitStateInvalidation インターフェースは、状態が正しく報告されていない場合に、対象デバイスへのサブスクリプションを通じて取得された状態を無効にする機能を提供します。状態が正しく報告されない例としては、Matter トレイトで「C」品質の属性を使用している場合や、デバイスの実装が原因で予期せず問題が発生する場合などがあります。

この API は、現在のトレイトの状態の強制読み取りを発行し、既存のトレイトフローを介して結果を返します。

トレイトを取得し、トレイトに対して forceRead を実行します。

val onOffTrait = device.?type(DimmableLightDevice)?.map{it.trait(OnOff)}.first()
onOffTrait.forceRead()

デバイスタイプのトレイトのリストを取得する

デバイスタイプは、デバイスを機能的な部分(Matter のエンドポイントなど)に分解するため、トレイトを読み取るエントリ ポイントとして使用する必要があります。

また、デバイスに 2 つのデバイスタイプがあり、両方に同じトレイトがある場合のトレイトの競合も考慮されています。たとえば、デバイスがスピーカーと調光ライトの両方である場合、2 つの On/Off トレイトと 2 つの Level Control トレイトがあります。

調光ライトのデバイスタイプで使用可能なトレイトのリストを取得するには:

// Get all types available on this device. Requires the types to be part of the registry during
// SDK initialization.
val typesFlow: Flow<Set<DeviceType>> = device.types()

// Get a snapshot of all types.
val types: Set<DeviceType> = typesFlow.first()

// Get the DimmableLightDevice instance from the set of types.
val dimmableLightDevice = types.filterIsInstance<DimmableLightDevice>().firstOrNull()

// Get all traits in the type + traits registered
val allTraits: Set<Trait> = dimmableLightDevice!!.traits()

別の種類のトレイトの競合は、デバイスに同じ名前のトレイトが 2 つある場合に発生する可能性があります。たとえば、onOff は標準の OnOff トレイトのインスタンスを参照することも、メーカー定義の OnOff トレイトのインスタンスを参照することもできます。どのトレイトが意図されているかについて、あいまいさを排除するため、デバイスを介して参照される Trait インスタンスの前に、修飾名前空間を付ける必要があります。標準トレイト(Matter 標準クラスタに類似するトレイト)の場合は、standardTraits を使用します。Google のトレイトの場合は、googleTraits を使用します。

// Accessing standard traits on the type.
val onOffTrait: OnOff? = dimmableLightDevice.standardTraits.onOff
val levelControlTrait: LevelControl? = dimmableLightDevice.standardTraits.levelControl

メーカー固有のトレイトにアクセスするには、直接参照します。

// Accessing a custom trait on the type.
val customTrait = dimmableLightDevice.trait(MyCustomTrait)

特定のトレイトを持つデバイスのリストを取得する

Kotlin の filter 関数を使用すると、API 呼び出しをさらに絞り込むことができます。たとえば、家にあるデバイスのうち、オン/オフ トレイトを持つすべてのデバイスのリストを取得するには、次のようにします。

// Get all devices that support OnOff
val onOffDevices: Flow<List<HomeDevice>> =
  home.devices().map { devices -> devices.filter { it.has(OnOff) } }

Google Home API で使用可能なトレイトの一覧については、Trait インターフェースをご覧ください。

類似するデバイスタイプのデバイスのリストを取得する

家にあるすべての照明を表すデバイスのリストを取得するには:

// Get a list of devices with similar device types (lights)
val lightDevices =
  home.devices().map { devices ->
    devices.filter {
      it.has(DimmableLightDevice) ||
        it.has(OnOffLightDevice) ||
        it.has(ColorTemperatureLightDevice) ||
        it.has(ExtendedColorLightDevice)
    }
  }

Home API には、コアデバイス タイプを表す複数のデバイスタイプがあります。たとえば、「ライト」というデバイスタイプはありません。代わりに、前述の例に示すように、ライトを表す 4 つの異なるデバイスタイプがあります。そのため、家にある上位レベルのデバイスを包括的に把握するには、フィルタされたフローに複数のデバイスタイプを含める必要があります。

Google Home API で使用可能なデバイスタイプの完全なリストについては、DeviceType インターフェースをご覧ください。

デバイスのベンダー ID またはプロダクト ID を取得する

BasicInformation トレイトには、デバイスのベンダー ID、プロダクト ID、商品名、シリアル番号などの情報が含まれます。

// Get device basic information. All general information traits are on the RootNodeDevice type.
val basicInformation = device.type(RootNodeDevice).first().standardTraits.basicInformation!!
println("vendorName ${basicInformation.vendorName}")
println("vendorId ${basicInformation.vendorId}")
println("productId ${basicInformation.productId}")

デバイス メーカー向けのクラウドツークラウド デバイス識別

デバイス メーカーが Cloud-to-cloud デバイスをビルドする場合、BasicInformation トレイトを介して Cloud-to-cloud デバイスを識別するには、SYNC レスポンスに次の文字列フィールドを含めます。

  • Connectivity Standards Alliance (Alliance) が発行したベンダー ID: "matterOriginalVendorId": "0xfff1",

  • ベンダーの商品を一意に識別する商品 ID: "matterOriginalProductId": "0x1234",

  • デバイスの固有識別子。メーカー固有の方法で作成されます。 "matterUniqueId": "matter-device-id",

これらの文字列フィールドを入力するときは、Matter ベンダー ID とプロダクト ID があれば、それらを使用します。Alliance メンバーではなく、これらの ID が割り当てられていない場合は、matterOriginalVendorId フィールドと matterOriginalProductId フィールドを空白のままにして、matterUniqueId を識別子として指定できます。

SYNC レスポンスの例では、これらのフィールドの使用方法を示しています。

{
  "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
  "payload": {
    "agentUserId": "1836.15267389",
    "devices": [
      {
        "id": "456",
        "type": "action.devices.types.LIGHT",
        "traits": [
          "action.devices.traits.OnOff",
          "action.devices.traits.Brightness",
          "action.devices.traits.ColorSetting",
        ],
        "willReportState": true,
        "deviceInfo": { ... },
        "matterOriginalVendorId": "0xfff1",
        "matterOriginalProductId": "0x1234",
        "matterUniqueId": "matter-device-id",
        "otherDeviceIds": [
          {
            "deviceId": "local-device-id",
          }
        ]
      }
    ]
  }
}

詳細については、Cloud-to-cloud SYNC のドキュメントをご覧ください。

デバイスとトレイトのメタデータ

Google Home API のデバイスとトレイトには、アプリでのユーザー エクスペリエンスの管理に役立つメタデータが関連付けられています。

Google Home API の各トレイトには sourceConnectivity プロパティが含まれています。このプロパティには、トレイトのオンライン ステータスとローカリティ(ローカルまたはリモート ルーティング)に関する情報が含まれています。

デバイスのプライマリ タイプを取得する

一部のデバイスは、Google Home API を通じて複数のデバイスタイプを提示することがあります。アプリでユーザーにデバイスの適切なオプション(デバイス コントロールやおすすめの自動化など)が表示されるようにするには、デバイスのメインのデバイスタイプを確認すると便利です。

まず、type() を使用してデバイスのタイプを取得し、次にプライマリ タイプを特定します。

val types = device.types().first()
val primaryTypes = types.filter { it.metadata.isPrimaryType }

トレイトがオンラインかどうかを確認する

connectivityState() メソッドを使用して、トレイトの接続性を確認します。

val onOffConnectivity = onOffTrait?.metadata?.sourceConnectivity?.connectivityState

一部のトレイト(通常は Google smart home トレイト)は、デバイスがインターネットに接続されていない場合、オフラインと表示されることがあります。これは、これらのトレイトがクラウドベースであり、ローカル ルーティングがないためです。

デバイスの接続性を確認する

一部のデバイスは複数のデバイスタイプをサポートしているため、デバイスの接続性は実際にはデバイスタイプ レベルで確認されます。返される状態は、そのデバイスのすべてのトレイトの接続状態を組み合わせたものです。

val lightConnectivity = dimmableLightDevice.metadata.sourceConnectivity.connectivityState

インターネット接続がない場合に、デバイスタイプが混在していると、PARTIALLY_ONLINE の状態が観察されることがあります。Matter 標準トレイトは、ローカル ルーティングによりオンラインのままになる可能性がありますが、クラウドベースのトレイトはオフラインになります。

デバイスの IP アドレスを取得する

デバイスの IP アドレスを確認するには、GeneralDiagnostics トレイトの networkInterfaces 属性を使用します。アドレスはバイト配列として返されます。これは、標準の IPv4 または IPv6 文字列にフォーマットできます。

val ipAddresses =
  trait.networkInterfaces?.flatMap { networkInterface ->
    (networkInterface.ipv4Addresses + networkInterface.ipv6Addresses).mapNotNull { bytes ->
      try {
        java.net.InetAddress.getByAddress(bytes).hostAddress
      } catch (e: java.net.UnknownHostException) {
        null
      }
    }
  } ?: emptyList()

トレイトのネットワーク ルーティングを確認する

特性のローカリティは、Google Home API でも利用できます。dataSourceLocality は、トレイトがリモート(クラウド経由)、ローカル(ローカルハブ経由)、ピアツーピア(デバイスからデバイスへの直接、ハブなし)のいずれでルーティングされるかを示します。

不明なローカリティ値 UNSPECIFIED は、たとえば、アプリが起動中で、デバイス接続用のハブまたはサーバーにまだ到達していない場合に発生する可能性があります。これらのデバイスはアクセスできず、コマンドやイベントからのインタラクション リクエストは失敗します。このようなデバイスをどのように処理するかは、クライアントが決定します。

val onOffLocality = onOffTrait?.metadata?.sourceConnectivity?.dataSourceLocality

デバイスのネットワーク ルーティングを確認する

接続性と同様に、ローカリティはデバイスタイプ レベルでチェックされます。返される状態は、そのデバイスのすべてのトレイトのローカリティを組み合わせたものです。

val lightLocality = dimmableLightDevice.metadata.sourceConnectivity.dataSourceLocality

MIXED の状態は、PARTIALLY_ONLINE 接続と同様のシナリオで観察されることがあります。一部のトレイトはクラウドベースで、他のトレイトはローカルです。

デバイスの名前を変更する

setName() メソッドを呼び出して、デバイスの名前を変更します。

mixerDevice.setName("Grendel")

名前が 60 Unicode コードポイント(文字)の上限を超えると、切り捨てられます。エラーはスローされません。長い名前の処理はデベロッパーの責任であり、たとえば、名前が切り捨てられることをユーザーに通知するかどうかを決定できます。

デバイスのグループ メンバーシップを確認する

1 台の実機を複数のデバイス グループに登録できます。デバイスが属するグループを確認するには、デバイスの GroupMembership トレイトを確認します。

val groupIds = device.trait(GroupMembershipTrait)?.groupObjectIds.orEmpty()
for (groupId in groupIds) {
  println("HomeAPI", "Device belongs to group: $groupId")
}

逆に、DeviceGroup で devices() フローを呼び出すことで、DeviceGroup に属するすべてのメンバー デバイスを監視できます。

group.devices(enableMultipartDevices = true).collect { members ->
  println("HomeAPI", "Group contains ${members.size} devices")
}

DeviceGroup でサポートされているデバイスタイプを検査し、単一のエンティティとしてグループにコマンドを送信するには、デバイス グループを制御するをご覧ください。