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

デバイス API には、iOS 用の Home API を介してアクセスできます。 以下のパッケージをアプリにインポートします。

import GoogleHomeSDK
import GoogleHomeTypes

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

エラー処理

Home API の一部のメソッドは HomeError をスローするため、do-catch ブロックを使用してこれらの呼び出しで HomeError をキャッチすることをおすすめします。

HomeError を処理するときは、codemessage フィールドを確認して、何が問題だったかを把握します。

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

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

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

呼び出しのサンプル

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

Home オブジェクトへの参照を使用して、 devices() を呼び出して、アクセス可能な デバイスの Query を取得します。Query's batched() メソッドを呼び出します。このメソッドは、デバイスのメタデータが変更されるたびに、ホームの現在の状態を反映する Set を出力します。または、 Query.list() を呼び出して、 利用可能なデバイスのスナップショットを取得します。これは、batched() ストリームをサブスクライブして最初に出力された値を返す便利なメソッドです。 Query.stream() は、デバイスのメタデータ(名前、部屋、構造など)が変更されると新しい値を出力するストリームを生成します。内部的には batched() を使用し、変更されたプロパティのみを出力します。

// Get a list of all devices accessible to the user
let homeDevices = try await self.home.devices().list()

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

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

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

デバイスに関連付けられたデバイスタイプを取得するには、デバイスの types プロパティを読み取ります。このプロパティは DeviceTypeControllerを返します。

DeviceTypeController.subscribe(_:) を呼び出して、特定のデバイスタイプの更新をサブスクライブします。

let devices = try await self.home.devices().list()
if let device = devices.first(where: { $0.id == myDeviceId }) {
  var receivedUpdate1 = false
  var receivedUpdate2 = false
  device.types.subscribe(OnOffLightDeviceType.self)
    .assertNoFailure()
    .sink { device in
      if !receivedUpdate1 {
        receivedUpdate1 = true
        Task {
          try await device.matterTraits.onOffTrait?.on()
        }
        return
      }
      if !receivedUpdate2 {
        receivedUpdate2 = true
        return
      }
      fatalError("Received unexpected update")
    }
}

デバイスが指定されたデバイスタイプをサポートしていない場合は、すぐに完了する Empty Publisher が返されます。

デバイスが特定のデバイスタイプをサポートしている場合は、get() を呼び出してそのタイプのハンドルを取得できます。

if let device = devices.first(where: { $0.id == myDeviceId }) {
  let _ = await device.types.get(OnOffLightDeviceType.self)
}

デバイスが指定されたタイプをサポートしていない場合は、nil が返されます。

DeviceTypeController.subscribeAll() を呼び出して、PublisherDeviceTypeCollection を取得します。 このクラスを使用すると、デバイスに特定のデバイスタイプがあるかどうかを確認できます。

if let device = devices.first(where: { $0.id == myDeviceId }) {
  device.types.subscribeAll()
    .assertNoFailure()
    .sink { types in
      let lightDeviceType = types[OnOffLightDeviceType.self]
      let fanDeviceType = types[FanDeviceType.self]
    }
}

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

デバイスタイプは、トレイトを読み取るためのエントリ ポイントです。デバイスを機能的な部分(Matter のエンドポイントなど)に分解します。Matter

また、デバイスに 2 つのデバイスタイプがあり、両方に同じトレイトが含まれている場合に、トレイトの衝突を考慮します。たとえば、デバイスがスピーカーと調光可能なライトの両方である場合、オン/オフのトレイトが 2 つ、レベル制御のトレイトが 2 つあります。

デバイスに同じ名前のトレイトが 2 つある場合にも、別の種類のトレイトの衝突が発生する可能性があります。たとえば、onOff は標準の OnOff トレイトのインスタンスを参照することも、メーカー定義の OnOff トレイトのインスタンスを参照することもできます。どのトレイトを意図しているかについての曖昧さを解消するには、各デバイスタイプの 2 つのトレイト コレクションのいずれかを介してトレイトを参照します。

標準トレイト( Matter 標準クラスタに類似)の場合は、matterTraits を使用します。たとえば、調光可能なライトのデバイスタイプの特定のトレイトを取得するには、次のようにします。

if let dimmableLightDeviceType =
  await device.types.get(DimmableLightDeviceType.self)
{
  // Accessing standard trait on the type.
  let levelControlTrait =
    dimmableLightDeviceType.matterTraits.levelControlTrait.self
}

Google トレイトの場合は、googleTraits を使用します。

if let doorbellDeviceType = await device.types.get(GoogleDoorbellDeviceType.self) {
  // Accessing Google trait on the type.
  let doorbellPressTrait =
    doorbellDeviceType.traits[Google.DoorbellPressTrait.self]
}

メーカー固有のトレイトにアクセスするには、traits プロパティを介して参照しますが、その前にメーカーのパッケージ名を付けます。

let deviceType = await device1?.types.get(OnOffLightDeviceType.self)
// Accessing custom trait on the type.
if let spinnerTrait = deviceType?.traits[ExampleOrganization.SpinnerTrait.self] {
  let rpmVal = spinnerTrait.attributes.rpm
}

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

デバイスのオン/オフのトレイトから OnOff 属性を確認する例を次に示します。

let lightDevices = devices.filter {
  $0.types.contains(OnOffLightDeviceType.self)
}
let light1 = lightDevices.first
let lightDeviceTypeOptional = await light1?.types.get(OnOffLightDeviceType.self)
if let onOffTrait = lightDeviceTypeOptional?.matterTraits.onOffTrait {
  let onOffVal = onOffTrait.attributes.onOff
}

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

特定のトレイトを持つデバイスのリストを取得するには、デバイス、各デバイスのデバイスタイプ、各デバイスタイプのトレイトを反復処理する必要があります。 たとえば、オン/オフのトレイトを持つ家にあるデバイスのリストを取得するには、次のようにします。

// Get all light devices that support levelControl
var levelControlDevices: [HomeDevice] = []
let allDevices = try await home.devices().list()
for device in allDevices {
  if let deviceType = await device.types.get(OnOffLightDeviceType.self) {
    if deviceType.traits.contains(Matter.LevelControlTrait.self) {
      levelControlDevices.append(device)
    }
  }
}

Home API で使用可能なトレイトの完全なリストについては、iOS のトレイト インデックスをご覧ください。

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

家にあるすべてのライトを表すデバイスのリストを取得するには、次のようにします。

// Get a list of devices with similar device types (lights)
let lightDevices =
  try await self.home.devices().list().compactMap {
    $0.types.contains(DimmableLightDeviceType.self)
      || $0.types.contains(OnOffLightDeviceType.self)
      || $0.types.contains(ColorTemperatureLightDeviceType.self)
      || $0.types.contains(ExtendedColorLightDeviceType.self)
  }

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

Home API で使用可能なデバイスタイプとそのトレイトの完全なリストについては、iOS でサポートされているデバイスタイプ をご覧ください。

デバイスのベンダー名、ベンダー ID、商品 ID を取得する

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

guard
  let vendorName =
    basicInfoTrait.attributes.vendorName
else {
  fatalError("Failed to get vendorName")
}
guard
  let vendorID =
    basicInfoTrait.attributes.vendorID
else {
  fatalError("Failed to get vendorID")
}
guard
  let productID =
    basicInfoTrait.attributes.productID
else {
  fatalError("Failed to get productID")
}

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

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

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

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

  • メーカー固有の方法で構築されたデバイスの一意の ID: "matterUniqueId": "matter-device-id",

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

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 ドキュメントをご覧ください

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

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

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

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

一部のデバイスでは、Home API を介して複数のデバイスタイプが表示されることがあります。 デバイスの適切なオプション(デバイスの制御や推奨される自動化など)がアプリでユーザーに表示されるようにするには、デバイスタイプがデバイスのプライマリ タイプであるかどうかを確認すると便利です。

if let deviceType =
  await device?.types.get(HumiditySensorDeviceType.self)
{
  if deviceType.metadata.isPrimaryType {
    print("Humidity Sensor is the primary type on this device.")
  } else {
    print("Humidity Sensor isn't the primary type on this device.")
  }
}

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

connectivityState プロパティを読み取って、トレイトの接続性を確認します。

let levelControlConnectivity =
  levelControlTrait.metadata.sourceConnectivity
  .connectivityState

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

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

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

let lightConnectivity =
  dimmableLightDeviceType.metadata.sourceConnectivity
  .connectivityState

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

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

デバイスの IP アドレスを確認するには、networkInterfaces 属性を使用します 。 GeneralDiagnosticsTrait アドレスは Data オブジェクトとして返されます。これは、Network フレームワークを使用して標準の IPv4 または IPv6 文字列にフォーマットできます。

func getIpAddresses(trait: Matter.GeneralDiagnosticsTrait) -> [String] {
  let interfaces = trait.attributes.networkInterfaces ?? []
  var ipAddresses: [String] = []

  for interface in interfaces {
    for data in interface.iPv4Addresses {
      if let ipv4 = IPv4Address(data) {
        ipAddresses.append(String(describing: ipv4))
      }
    }
    for data in interface.iPv6Addresses {
      if let ipv6 = IPv6Address(data) {
        ipAddresses.append(String(describing: ipv6))
      }
    }
  }

  return ipAddresses
}

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

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

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

let levelControlLocality =
  levelControlTrait.metadata.sourceConnectivity
  .dataSourceLocality

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

接続と同様に、ロケールはデバイスタイプ レベルで確認されます。返される状態は、そのデバイスのすべてのトレイトのロケールの組み合わせです。

let lightLocality =
  dimmableLightDeviceType.metadata.sourceConnectivity.dataSourceLocality

partiallyOnline 接続と同様のシナリオで、mixed の状態になることがあります。一部のトレイトはクラウドベースですが、他のトレイトはローカルです。

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

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

let updatedDevice = try await theDevice.setName("new device name")

デバイスの名前を変更しても、元の HomeDevice 構造体は変更されず、返される更新された HomeDevice オブジェクトに反映されます。

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