iOS でデバイスを操作する

このガイドは、iOS でデバイスとデバイス メタデータにアクセスするの続きであり、デバイスの制御とアクセスのその他の例を紹介します。

特定のデバイスタイプまたはトレイト(ここでの多くの例で使用されている Matter OnOffTrait など)を使用するには、それらをインポートする必要があります。

import GoogleHomeSDK
import GoogleHomeTypes

トレイトがコマンドをサポートしているかどうかを確認する

トレイトレベルの supports 関数を使用して、特定のデバイスでコマンドがサポートされているかどうかを確認します。

たとえば、デバイスが On/Off トレイトの toggle コマンドをサポートしているかどうかを確認するには、次のようにします。

// Check if the OnOff trait supports the toggle command.
if onOffTraitTest.supportsToggleCommand {
  print("onOffTrait supports toggle command")
} else {
  print("onOffTrait does not support stateful toggle command")
}

デバイスにコマンドを送信する

コマンドの送信は、トレイトから状態属性を読み取るのと似ています。デバイスのオンとオフを切り替えるには、OnOffTrait Toggle コマンドを使用します。このコマンドは、Google Home エコシステム データモデルで toggle() として定義されています。このメソッドは、onOff が true の場合は false に、false の場合は true に変更します。

// Calling a command on a trait.
do {
  try await onOffTraitTest.toggle()
} catch let _ as HomeError {
  // Code for handling the exception
}

実行フローで問題が検出されると、コマンドが例外を返すことがあります。デベロッパーは、do-catch ブロックを使用してこれらの例外を適切に処理し、エラーが対処可能なケースについて詳細な情報をユーザーに表示する必要があります。処理されない例外はアプリのランタイムを停止し、アプリのクラッシュにつながる可能性があります。

または、off() コマンドまたは on() コマンドを使用して、状態を明示的に設定します。

do {
  try await onOffTraitTest.off()
  try await onOffTraitTest.on()
} catch let _ as HomeError {
  // Code for handling the exception
}

状態を変更するコマンドを送信した後、完了したら、デバイスの状態を読み取るで説明されているように状態を読み取り、アプリで処理できます。

パラメータを含むコマンドを送信する

一部のコマンドでは、OnOffTrait や LevelControlTrait などのパラメータを使用できます。

offWithEffect

// Turn off the light using the DyingLight effect.
do {
  try await onOffTraitTest.offWithEffect(
    effectIdentifier: Matter.OnOffTrait.EffectIdentifierEnum.dyingLight,
    effectVariant: 0
  )
} catch let _ as HomeError {
  // Code for handling the exception
}

moveToLevel

// Change the brightness of the light to 50%
do {
  try await levelControlTraitTest.moveToLevel(
    level: UInt8(127),
    transitionTime: 0,
    optionsMask: Matter.LevelControlTrait.OptionsBitmap(),
    optionsOverride: Matter.LevelControlTrait.OptionsBitmap()
  )
} catch let _ as HomeError {
  // Code for handling the exception
}

トレイトが属性をサポートしているかどうかを確認する

デバイスによっては、Matter トレイトをサポートしていても、特定の属性をサポートしていない場合があります。たとえば、Matter にマッピングされた Cloud-to-cloud デバイスは、すべての Matter 属性をサポートしていない可能性があります。このようなケースに対応するには、トレイトレベルの isSupported プロパティを使用して、特定のデバイスで属性がサポートされているかどうかを確認します。

たとえば、デバイスが On/Off トレイトの onOff 属性をサポートしているかどうかを確認するには、次のようにします。

// Check if the OnOff trait supports the onOff attribute.
if onOffTrait.attributes.$onOff.isSupported {
  print("onOffTrait supports onOff state")
} else {
  print("onOffTrait is for a command only device!")
}

一部の属性は、Matter 仕様または Cloud-to-cloud smart home スキーマで null 値を許容します。これらの属性の場合、属性によって返される nil が、デバイスがその値を報告していないことが原因なのか、属性の値が実際に nil であるのかを判断するには、isSupported に加えて isNullable を使用します。

// Check if a nullable attribute is set or is not supported.
if let deviceType = await device.types.get(OnOffLightDeviceType.self) {
  if let onOffTrait = deviceType.traits[Matter.OnOffTrait.self] {
    if onOffTrait.attributes.startUpOnOff == nil {
      if onOffTrait.attributes.$startUpOnOff.isSupported {
        print(
          "onOffTrait supports startUpOnOff and it is nil. Check the spec for the contextual meaning."
        )
      } else {
        print("onOffTrait does not support startUpOnOff!")
      }
    } else {
      print(
        "onOffTrait supports startUpOnOff and it is set to \(String(describing: onOffTrait.attributes.startUpOnOff))"
      )
    }
  }
}

特性属性を更新する

特定の属性の値を変更したいが、トレイトのコマンドのいずれも変更しない場合、属性は値を明示的に設定することをサポートしている可能性があります。

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

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

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

したがって、属性の値の変更方法を決定するプロパティの組み合わせは次のとおりです。

  • 読み取り専用で、他のコマンドの影響を受けません。つまり、属性の値は変更されません。たとえば、 SwitchTrait の currentPosition 属性です。

  • 読み取り専用で、他のコマンドの影響を受けます。つまり、属性の値を変更する唯一の方法は、コマンドを送信することです。たとえば、LevelControlTrait の currentLevel 属性は読み取り専用ですが、その値は moveToLevel などのコマンドによって変更できます。

  • 書き込み可能で、他のコマンドの影響を受けません。つまり、トレイトの update 関数を使用して属性の値を直接変更できますが、属性の値に影響するコマンドはありません。たとえば、 DoorLockTrait の WrongCodeEntryLimit 属性です。

  • 書き込み可能で、他のコマンドの影響を受けます。つまり、トレイトの update 関数を使用して属性の値を直接変更できます。また、コマンドを送信した結果として属性の値が変更されることもあります。たとえば、 ThermostatTrait の occupiedCoolingSetpoint 属性 は書き込み可能ですが、 setpointRaiseLower コマンドで更新することもできます。

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

次の例は、DoorLockTrait.wrongCodeEntryLimit 属性の値を明示的に設定する方法を示しています。

属性値を設定するには、トレイトの update 関数を呼び出し、新しい値を設定する更新関数を渡します。まず、トレイトが属性をサポートしていることを確認することをおすすめします。

次に例を示します。

if doorLockTraitTest.attributes.$wrongCodeEntryLimit.isSupported {
  let _ = try await doorLockTraitTest.update {
    $0.setWrongCodeEntryLimit(3)
  }
}

デバイス グループを操作する

メンバー デバイスを反復処理することで、グループのすべてのメンバーをきめ細かく制御できるだけでなく、DeviceGroup を単一のエンティティとして制御することもできます。DeviceGroup は HasParts プロトコルに準拠し、グループ内のデバイスでサポートされている DeviceType 部分を公開します。

  • await group.part(_:) は、グループがサポートしている場合は指定された DeviceType の最初のインスタンスを返し、サポートしていない場合は nil を返します。
  • try await group.parts(_:) は、指定された DeviceType に一致するすべての最上位部分の配列を返します。

DeviceGroup の一部であるデバイスタイプでトレイト コマンドを呼び出すと、SDK は、基盤となる接続やトランスポート(Matter や Cloud-to-cloud など)に関係なく、その機能をサポートするグループ内の関連するすべてのメンバー デバイスにコマンドをリレーします。

グループ部品特性属性とエラー処理

グループの一部をターゲットに設定すると、グループ全体にべき等コマンド(照明のオン / オフ、明るさの設定など)を送信する一般的なケースが簡素化されます。DeviceGroup 部分でトレイトとコマンドを操作する場合は、次の動作に注意してください。

  • 動的状態属性(OnOffTrait の onOff 属性や LevelControlTrait の currentLevel 属性など)は、通常、グループ部分のトレイトには入力されません。グループ内のメンバー デバイスは、単一の値に結合できない異なる状態になる可能性があるためです。ただし、サポートされているコマンド(supports(_:) で確認。調光可能なライトの .moveToLevelWithOnOff など)と、選択した機能属性(カラーライトの ColorControlTrait の colorTempPhysicalMinMireds や colorTempPhysicalMaxMireds など)は、アプリが UI コントロールを適切に構成できるように、入力されます。
  • グループ メンバーのトレイトのすべてのコマンドがグループ パーツで使用できるわけではありません。特に、値を返すコマンドは使用できません。
  • グループ部分に対するコマンドは、基盤となる各メンバー デバイス コマンドの個別の結果を返しません。個々のコマンドの結果を検査する場合や、値を返すコマンドを呼び出す場合は、グループのメンバー デバイスを直接反復処理します。
  • DeviceGroup の一部に送信されたコマンドは、メンバー デバイス全体にわたるファンアウト全体が終了し、基盤となるすべてのコマンドの成功または失敗が収集されると完了します。コマンドが失敗する(HomeError をスローする)のは、関連付けられているすべてのメンバー デバイスでコマンドが失敗した場合、またはすべての対象デバイスが利用できない場合のみです。これは、単一コマンドの呼び出し構文に一致する単一コマンドの失敗として表示されます。少なくとも 1 つのメンバー デバイスがコマンドを実行すると、グループ コマンドはエラーをスローせずに完了します。

グループの一部にコマンドを送信する

デバイス グループ内のすべての OnOffLightDeviceType ライトをオンにするには:

if let lightPart = await group.part(OnOffLightDeviceType.self),
   let onOffTrait = lightPart.matterTraits.onOffTrait,
   onOffTrait.supports(.on) {
  try await onOffTrait.on()
}

また、複数の関連するデバイスタイプを確認することもできます(たとえば、OnOffTrait をサポートするグループ内の照明デバイスタイプをオフにするなど)。

func onOffTrait(in group: DeviceGroup) async -> Matter.OnOffTrait? {
  if let light = await group.part(OnOffLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  if let light = await group.part(DimmableLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  if let light = await group.part(ColorTemperatureLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  if let light = await group.part(ExtendedColorLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  return nil
}

if let trait = await onOffTrait(in: group) {
  try await trait.off()
}

コマンドを送信する前に、グループ部分のコマンド サポートまたは読み取り機能の境界を確認できます(たとえば、調光可能なライト部分で .moveToLevelWithOnOff を確認したり、色温度ライト部分で colorTempPhysicalMinMireds と colorTempPhysicalMaxMireds を読み取ったりできます)。

if let dimmableLight = await group.part(DimmableLightDeviceType.self),
   let levelTrait = dimmableLight.matterTraits.levelControlTrait,
   levelTrait.supports(.moveToLevelWithOnOff) {
  try await levelTrait.moveToLevelWithOnOff(
    level: 127,
    transitionTime: nil,
    optionsMask: [],
    optionsOverride: []
  )
}

if let colorLight = await group.part(ColorTemperatureLightDeviceType.self),
   let colorTrait = colorLight.matterTraits.colorControlTrait,
   let minMireds = colorTrait.attributes.colorTempPhysicalMinMireds,
   let maxMireds = colorTrait.attributes.colorTempPhysicalMaxMireds {
  let midMireds = UInt16((UInt32(minMireds) + UInt32(maxMireds)) / 2)
  try await colorTrait.moveToColorTemperature(
    colorTemperatureMireds: midMireds,
    transitionTime: 0,
    optionsMask: [],
    optionsOverride: []
  )
}