iOS で Home API を使用してモバイルアプリを作成する

1. はじめに

f154e30306882c74.png

Home API とは何ですか?

Google Home API には、デベロッパーが Google Home エコシステムを活用するための一連のライブラリが用意されています。Home API を使用すると、デベロッパーはスマートホーム デバイスをシームレスに設定して制御するアプリを構築できます。

3e11583c779a2cec.png

Home API のコンポーネント

Home API は次の要素で構成されています。

  • Device API と Structure API: ユーザーの家に操作を実行します。アプリはこれらの API を使用して、デバイス、部屋、構造物に関する情報を読み取ったり(現在のサーモスタットの温度を確認するなど)、デバイスを操作したりできます(サーモスタットの設定温度を変更するなど)。
  • Commissioning API: 最小限の労力で、新しい Matter デバイスをファブリックにコミッショニング(セットアップ)します。
  • Automation API: ユーザーの家で実行される自動化を作成、削除、クエリします。

前提条件

  • Xcode の最新の安定版。
  • 家に少なくとも 1 つの構造物がある Google アカウント。
  • iOS 16.4 以降を搭載し、テスト アカウントでセットアップされた iOS デバイス。
  • Apple Developer Program に登録されている Apple ID(プロビジョニング プロファイルを生成するため)。
  • Home API をサポートする Google ハブ

学習内容

  • ベスト プラクティスに沿って Home API を使用して iOS アプリを作成する方法。
  • Device API と Structure API を使用してスマートホームを表現し、制御する方法。
  • Commissioning API を使用して Google Home エコシステムにデバイスを追加する方法。
  • Automation API を使用して基本的な自動化を作成する方法。

2. 家を設定する

デバイスを準備する

Google Home Playground には、さまざまな事前ビルド済みのエミュレートされたスマートホーム デバイスが用意されています。特に、家にあるデバイスの数に限りがある場合は、Home API の可能性を最大限に引き出すために使用することをおすすめします。

手順に沿って Google Home Playground にログインし、Google Home アプリアカウントのリンクを完了します。完了すると、Google Home アプリの [デバイス] タブにデバイスが表示されます。

c892afce113abe8f.png

3. 設定方法

サンプルアプリのコードを取得する

まず、GitHub からソースコードのクローンを作成します。

git clone https://github.com/google-home/google-home-api-sample-app-ios.git

サンプル ディレクトリには、この Codelab 用の start ブランチと finished ブランチが含まれています。

  • start: このプロジェクトのスターター コード。これに変更を加えて Codelab を完了します。
  • finished: この Codelab の完成したコード。作業のチェックに使用します。

「start」コードを確認する

この Codelab を開始するには、クローンされたリポジトリの start ブランチに切り替えます。

git checkout start

このブランチには、プロジェクトのスターター コードが含まれています。この Codelab では、このコードを変更して、すべての機能を実装します。Codelab のサンプルアプリには、Home APIs iOS SDK を操作するための Swift で構築された基本的な構造が用意されています。start プロジェクトの主なコンポーネントを簡単に見てみましょう。

  • Main Entry (GoogleHomeAPISampleIOSApp): GoogleHomeAPISampleIOS/Main/GoogleHomeAPISampleIOS.swift にある、アプリのメイン エントリ ポイントです。SDK を構成して初期化し、メインのユーザー インターフェースを設定します。
  • Core Views (View/):
    • MainView.swift: 起動後のルートビュー。メインの NavigationView が含まれています。アクティブな Google Home ストラクチャの選択を処理し、対応する StructureView を表示します。
    • StructureView.swift: 現在選択されている構造のコンテンツを表示します。タブを使用して、デバイスのグリッドと自動化リストを切り替えることができます。部屋やデバイスを追加するためのメニューも表示されます。
    • DeviceView.swift: StructureView グリッド内の単一デバイスのインタラクティブ タイルを表します。
    • AutomationsView.swift: 構造の既存の自動化のリストが表示され、自動化の詳細を作成または表示するためのナビゲーションが表示されます。
  • ViewModels (ViewModel/): これらのクラスは、ビューの状態とロジックを管理します。
    • AccountViewModel.swift: Home オブジェクトへの接続を処理し、認証状態を管理します。
    • MainViewModel.swift: 使用可能な Structure オブジェクトのリストを管理し、選択した構造を追跡します。
    • StructureViewModel.swift: 選択した構造物内の部屋と DeviceControl オブジェクトの表示を管理します。
    • AutomationList.swiftAutomationViewModel.swift など: 自動化の取得、表示、作成、管理を処理します。
  • Device Controls (ViewModel/Device/):
    • DeviceControl.swift: 制御可能なデバイスを UI で表すための基本クラス。
    • 特定のサブクラス(LightControl.swiftFanControl.swiftOnOffPlugInUnitControl.swift など): デバイスの種類に基づいて、その特性に基づいて UI ロジック、デバイス制御、状態マッピングを実装します。
    • DeviceControlFactory.swift: 特定の HomeDevice に適した DeviceControl サブクラスの作成を担当します。
  • Commissioning (Commissioning/):
    • CommissioningManager.swift: Matter デバイスの設定フローを管理するロジックが含まれています。
  • Utilities & UX (Utils/, UX/, Storage/): UI 要素(色、サイズ)、エラー処理、データ ストレージ(SelectedStructureStorage.swift)、その他のユーティリティのヘルパーコードが含まれます。

この Codelab では、start プロジェクト内に TODO などのコメントや、コメントアウトされたコードブロックとアラートがあります。これらのマークは、提供されている手順に沿って、必要な機能を実装するためにコードを追加またはコメント化解除するセクションを示しています。

Apple デプロイ構成ファイルを作成する

App Atest を構成するには、Apple デプロイ構成ファイルの作成手順に沿って操作します。セットアップ後にアプリをデプロイできるのは、シミュレータではなく実機に限られます。

認証を設定する

OAuth クライアント ID を取得して Home API を有効にするには、まず Google Cloud にログインし、新しいプロジェクトを作成するか、既存のプロジェクトを選択します。次に、手順に沿って OAuth クライアント ID を生成して Home API を有効にし、アカウントを許可リストに追加します。

SDK を設定する

Home APIs iOS SDK を取得し、SDK を設定するに記載されている設定手順に沿って設定します。HOME_API_TODO_ADD_APP_GROUP は、独自のアプリグループに置き換えてください。

プロジェクトをビルドして実行する

start ブランチでプロジェクトをビルドして実行すると、TODO ダイアログと「ログインが必要です」と表示された画面が表示されます。Home APIs の操作は、以降のセクションで実装します。

bd56b7080037e38a.png 9c0f08a3f4197a77.png

: ダイアログに表示されたテキストをプロジェクト内で検索して、変更が必要なコードを見つけます。たとえば、「TODO: initialize Home」を検索します。

4. 初期化

家を初期化する

iOS 向けの Home API を使用する前に、アプリで Home を初期化する必要があります。Home は SDK の最上位エントリであり、ユーザーの構造内のすべてのエンティティにアクセスできます。特定のタイプのすべてのエンティティをリクエストすると、API は結果の受信方法を選択できる Query オブジェクトを返します。GoogleHomeAPISampleIOS/Accounts/AccountViewModel.swift で、connect() のコメントとアラートを削除して、家の初期化を実装します。

  /// TODO: initialize Home
  /// Remove comments to initialize Home and handling permission.
  private func connect() {
    Task {
      do {
        self.home = try await Home.connect()
      } catch {
        Logger().error("Auth error: \(error).")
      }
    }
  }

Home API を使用するための権限

アプリを実行すると、同意画面が表示されます。Google Home ストラクチャを選択し、Google Cloud プロジェクトの許可リストにあるアカウントを選択します。

47310f458c0094d9.png 4a571dbd9979a88c.png e29c75891a3a67af.png

5. デバイスと構造

部屋とデバイスを取得する

GoogleHomeAPISampleIOS/ViewModel/StructureViewModel.swift で、getRoomsAndDevices() のコメントとアラートを削除して、選択した構造内の部屋とデバイスをそれぞれ home.rooms()home.devices() で取得します。

  /// TODO: get rooms and devices
  /// Remove comments to get the rooms and devices from home entry
  private func getRoomsAndDevices(){
    self.home.rooms().batched()
      .combineLatest(self.home.devices().batched())
      .receive(on: DispatchQueue.main)
      .catch { error in
        Logger().error("Failed to load rooms and devices: \(error)")
        return Just((Set<Room>(), Set<HomeDevice>()))
      }
      .map { [weak self] rooms, devices in
        guard let self = self else { return [] }
        self.hasLoaded = true
        return self.process(rooms: rooms, devices: devices)
      }
      /// receive from .map and .assign() to publisher entries
      .assign(to: &self.$entries)
  }

process() 関数は、まずデバイスが同じ部屋にあることを確認してから、DeviceControlDeviceControlFactory を使用してデバイスを HomeDevices として操作します。

4c677c4c294e67ca.png

: デバイスが DeviceControlFactory にリストされていない場合は、「サポート対象外」と表示されます。サポートされているデバイスの詳細については、iOS でサポートされているデバイスの種類のページをご覧ください。

デバイスを操作する

プラグ outlet1 は、デバイスをタップまたはスライドしても最初は非アクティブです。操作を有効にするには、GoogleHomeAPISampleIOS/ViewModel/Device/OnOffPlugInUnitControl.swift を見つけて、primaryAction() 関数内のコメントとアラートを削除します。

  /// TODO: primary action of OnOffPlug
  /// Toggles the plug; usually provided as the `action` callback on a Button.
  public override func primaryAction() {
    self.updateTileInfo(isBusy: true)
    Task { @MainActor [weak self] in
      guard
        let self = self,
        let onOffPluginUnitDeviceType = self.onOffPluginUnitDeviceType,
        let onOffTrait = onOffPluginUnitDeviceType.matterTraits.onOffTrait
      else { return }

      do {
        try await onOffTrait.toggle()
      } catch {
        Logger().error("Failed to to toggle OnOffPluginUnit on/off trait: \(error)")
        self.updateTileInfo(isBusy: false)
      }
    }
  }

OnOffPlugInUnitControl クラス内の primaryAction() 関数は、スマートプラグまたは OnOffPluginUnitDeviceType で表されるデバイスのオン/オフ状態を切り替えます。

その他のデバイスの制御の例については、GoogleHomeAPISampleIOS/ViewModel/Device をご覧ください。

新しい部屋を作成する

Structure API を使用すると、部屋の作成と削除、部屋間のデバイスの移動を行うことができます。

GoogleHomeAPISampleIOS/ViewModel/StructureViewModel.swift で、addRoom() のコメントとアラートを削除します。

  /// TODO: add room
  /// Add a new room in a given structure.
  func addRoom(name: String, structure: Structure) {
    Task {
      do {
        // The view will be updated with the values from the devices publisher.
        _ = try await structure.createRoom(name: name)
      } catch {
        Logger().error("Failed to create room: \(error)")
      }
    }
  }

structure.createRoom() で新しい部屋を作成するには、左上の [+] アイコン > [部屋を追加] を選択します。新しいチャットルーム名を入力し、[チャットルームを作成する] をクリックします。数秒後に新しい部屋が表示されます。

b122ae6642b7da1c.png a45f785e1d51938e.png 7753b56cbdcff8d6.png

デバイスを別の部屋に移動する

GoogleHomeAPISampleIOS/ViewModel/StructureViewModel.swift で、moveDevice() のコメントとアラートを削除します。

  /// TODO: move device
  /// Move a device into a different room.
  func moveDevice(device deviceID: String, to roomID: String, structure: Structure) {
    Task {
      do {
        _ = try await structure.move(device: deviceID, to: roomID)
      } catch {
        Logger().error("Failed to move to room: \(error)")
      }
    }
  }

structure.move() でデバイスを移動するには、デバイスを長押しして [別の部屋に移動] を選択し、新しい部屋を選択します。

f9627592af44163d.png fd126fabb454f2bf.png 813e1e23e50cd9f6.png

空の部屋を削除する

GoogleHomeAPISampleIOS/ViewModel/StructureViewModel.swift で、removeRoom() のコメントとアラートを削除します。

  /// TODO: delete room
  /// Delete an empty room in a given structure.
  func removeRoom(id: String, structure: Structure) {
    Task {
      do {
        // The view will be updated with the values from the devices publisher.
        _ = try await structure.deleteRoom(id: id)
      } catch {
        Logger().error("Failed to remove room: \(error)")
      }
    }
  }

structure.deleteRoom()空のルームを削除するには、部屋の名前の右側にあるゴミ箱アイコンをクリックし、操作を確定します。削除できるのは空の部屋のみです。

4f129262ad67f564.png

: デバイスを元の部屋に戻して、空の部屋を作成します。

6. コミッショニング

: このセクションでは、Google Hub と Matter デバイスが必要です。ストラクチャ内の Google ハブがオンラインで、アクセス可能であることを確認します。Matter デバイスをお持ちでない場合は、代わりに Matter Virtual Device アプリを使用してください。

Matter デバイスを追加する

Commissioning API を使用すると、アプリでユーザーの家に新しい Matter デバイスを追加したり、Google アカウントに追加したりできます。これにより、アプリ内でシームレスに設定できるようになります。

GoogleHomeAPISampleIOS/Commissioning/CommissioningManager.swift で、addMatterDevice() のコメントとアラートを削除します。

  /// TODO: add Matter Device
  /// Starts the Matter device commissioning flow to add the device to the user's home.
  /// - Parameters:
  ///   - structure: The structure to add the device to.
  ///   - add3PFabricFirst: Whether to add the device to a third party fabric first.
  public func addMatterDevice(to structure: Structure, add3PFabricFirst: Bool) {
    self.isCommissioning = true

    /// pass if it's 1p or 3p commissioning
    let userDefaults = UserDefaults(
      suiteName: CommissioningManager.appGroup)
    userDefaults?.set(
    add3PFabricFirst, forKey: CommissioningUserDefaultsKeys.shouldPerform3PFabricCommissioning)

    Task {
      do {
        try await structure.prepareForMatterCommissioning()
      } catch {
        Logger().error("Failed to prepare for Matter Commissioning: \(error).")
        self.isCommissioning = false
        return
      }

      // Prepare the Matter request by providing the ecosystem name and home to be added to.
      let topology = MatterAddDeviceRequest.Topology(
        ecosystemName: "Google Home",
        homes: [MatterAddDeviceRequest.Home(displayName: structure.name)]
      )
      let request = MatterAddDeviceRequest(topology: topology)

      do {
        Logger().info("Starting MatterAddDeviceRequest.")
        try await request.perform()
        Logger().info("Completed MatterAddDeviceRequest.")
        let commissionedDeviceIDs = try structure.completeMatterCommissioning()
        Logger().info("Commissioned device IDs: \(commissionedDeviceIDs).")
      } catch let error {
        structure.cancelMatterCommissioning()
        Logger().error("Failed to complete MatterAddDeviceRequest: \(error).")
      }

      self.isCommissioning = false
    }
  }

structure.prepareForMatterCommissioning() で新しい部屋を作成するには、左上の「+」アイコン > [Google Fabric にデバイスを追加] を選択します。MatterAddDeviceRequest を使用して、Matter デバイスを部屋に追加します。部屋とデバイス名を選択すると、デバイスが [デバイス] 画面に表示されます。

adf6cbb531787aaf.png f002bd6320bc480d.png

7. 自動化

ストラクチャ内のすべての自動化を表示する

下部にあるナビゲーション バーの [自動化] をタップします。structure.listAutomations() を使用すると、構造内のすべての自動化が一覧表示されます。

cc6d50f72f812c24.png

: スマートホームを設定していない場合は、[自動化を追加して開始] というメッセージが表示されます。

自動化の作成

Device API と Structure API について理解し、新しいデバイスを追加できたので、Automation API を使用して新しい自動化を作成しましょう。

GoogleHomeAPISampleIOS/ViewModel/Automation/AutomationsRepository.swift で、lightAutomation() のコメント、アラート、空の自動化を削除します。

  /// TODO: create automation
  /// - Parameter devices: devices in current selected structure
  /// - Returns: the automation object to be created
  /// This automation will turn off the light after 5 seconds.
  public func lightAutomation(devices: Set<HomeDevice>) async throws -> any DraftAutomation {
    let light = devices.first { $0.name == "light2" }
    
    guard let light else {
      Logger().error("Unable to find light device with name light2")
      throw HomeError.notFound("No devices support OnOffLightDeviceType")
    }
    
    return automation(
      name: "Turn off light after 5 seconds",
      description:
        """
        Turns off light2 after it has been on for 5 seconds.
        """
    ) {
      let onOffStarter = starter(light, OnOffLightDeviceType.self, OnOffTrait.self)
      onOffStarter
      condition {
        onOffStarter.onOff.equals(true)
      }
      delay(for: Duration.seconds(5))
      action(light, OnOffLightDeviceType.self) {
        OnOffTrait.off()
      }
    }
  }

照明をオンにしてから 5 秒後にオフにする自動化を作成するには、自動化ビューに移動して [+ 追加] ボタンをクリックします。[5 秒後にライトをオフにする] を選択します。starterconditionaction など、自動化の詳細が表示されます。[保存] をクリックして、structure.createAutomation() による自動化を作成します。

21c1f8ea2a29134b.png 4bd36f6ed9c5f6e9.png

: 利用できる自動化は、ご自宅にあるデバイスによって異なります。利用可能な自動化が表示されない場合は、照明デバイスの名前を「light2」に変更してみてください。

[デバイス] タブに戻り、「light2」という名前のライトのオンとオフを切り替えます。5 秒後に自動的にオフになります。

自動化のコンポーネントは次のとおりです。

  • 開始条件: 自動化を開始するイベントです。この例では、OnOffTrait に変更があると自動化が開始されます。
  • 条件: 開始デバイスが特定の要件を満たしているかどうかを確認します。この場合、照明がオンになっている場合に自動化が実行されます。
  • アクション: 開始条件が要件を満たしている場合にのみ、実行する自動化です。条件が満たされると、ライトがオフになります。

その他の例については、自動化の例のページをご覧ください。

自動化を削除する

structure.deleteAutomation() メソッドは、既存の自動化を左にスワイプしてゴミ箱アイコンをタップし、構造から削除すると呼び出されます。

dc678cd9e16f89a5.png

8. 完了

これで、iOS 向け Home API を使用して、基本的なスマートホーム アプリを作成しました。

学習内容:

  • 初期化: Home.connect() を使用してアプリを Google Home エコシステムに接続しました。
  • 権限: 家のデータをアクセスするためのユーザー認証と認可を処理しました。
  • デバイスと構造: home.rooms()home.devices() を使用して、部屋とデバイスを取得して表示しました。
  • デバイスの制御: トレイトでコマンドを呼び出して OnOffPluginUnitDeviceType の状態を切り替えるなどの、デバイス操作を実装しました。
  • 構造の管理: 新しい部屋を作成する機能(structure.createRoom())、部屋間でデバイスを移動する機能(structure.move())、空の部屋を削除する機能(structure.deleteRoom())を追加しました。
  • 構成: SDK の構成フローを統合して、新しい Matter デバイスを追加しました(MatterAddDeviceRequest)。
  • 自動化: 構造内の自動化を一覧表示する方法、作成する方法(structure.createAutomation())、削除する方法(structure.deleteAutomation())について説明しました。

これで、Home API を利用して iOS で豊富なスマートホーム コントロール エクスペリエンスを構築する方法の基本を理解できました。

次のステップ:

  • サンプルアプリに用意されている他のデバイスタイプ(照明、扇風機、ブラインドなど)の操作を試す。
  • さまざまなデバイスで利用可能なさまざまな特徴とコマンドについて詳しく説明します。
  • さまざまな開始条件、条件、アクションを使用して、より複雑な自動化を作成してみてください。
  • 高度な機能と詳細については、Home APIs のドキュメントをご覧ください。

お疲れさまでした。