スマートホームデバイス的な何かを作る

# １．はじめに
　前回の[OAuth 2.0 Server PHP と Google OAuth2.0 Playground を接続する](https://qiita.com/SSaitou/items/26fc907aa1ef9c5dba88 ) でOAuth2.0サーバーが使えるようになったので、本稿ではデバイスのような振る舞いをするものを作って行こうと思います。正確にはデバイスを管理するスマートフォンアプリ的なものです。デバイスがあるかのような応答をさせようというものです。あくまでもテストとして。

　流れとしては以下の通りです。
1. アカウントリンクを作成する
2. フルフィルメントを登録する
3. モックアップを作る
4. グーグルホームアプリに登録する

　フルフィルメントは要するにＡＰＩですが、とりあえず応答らしきものをするモックアップを作成し、デバイスとして登録されることを本稿の目標にします。
# ２．アカウントリンクを作成する
　アカウントリンク実装についての概要は[ドキュメント](https://developers.google.com/assistant/smarthome/develop/implement-oauth#configure_account_linking_in_the_console )で説明されています。[アクションコンソール](https://console.actions.google.com/u/0/ )に移動し、まず新規プロジェクトを作成します。
1. New project をクリックして、Project Nameに入力。language や countryも適当に。
2. 作成したプロジェクトをクリック
3. Quick setup をクリック
4. Name your Smart Home actionをクリックし、Display name を設定する。
   このDisplay nameはアシスタントアプリが読み上げに使います。
5. Setup account linking をクリックし、クライアントＩＤ、クライアントシークレットを設定し、これまで作ってきたOAuth2.0サーバーの認証ＵＲＩ、トークンＵＲＩを設定します。それと、これが必要かどうかは今一つ解っていないのですが、Configure your client (optional)でScopesにbasicを設定します。
6. 右上のSaveをクリックする

　また、クライアントＩＤとクライアントシークレット、リダイレクトＵＲＩをリソースＤＢに登録しておきます。リダイレクトＵＲＩについては[アカウントリンクの実装](https://developers.google.com/assistant/smarthome/develop/implement-oauth )内で触れられていますが、次のような形式になります。
`https://oauth-redirect.googleusercontent.com/r/YOUR_PROJECT_ID`

# ３．フルフィルメントを登録する
　アカウントリンクの作成に続いて、フルフィルメントも設定します。
1. 左ペーンにあるメニューのActionsをクリックし、Fullfilment URLに呼び出し用のＵＲＩを設定します
  `https://your.domain/gsm_api.php`
2. 右上のSaveをクリックする

# ４．グーグルホームアプリに登録する
　作成したプロジェクトをデバイスとしてグーグルホームアプリに登録します。グーグルホームアプリを起動して、設定→デバイスの追加を選び、検索窓で登録したDISPLAY NAMEを入力します。
　[TEST] DISPLAY NAMEのような表示がされると思うので、タッチすれば登録される……のですが、今はフルフィルメントに登録したＵＲＩの実体が無いのでエラーになって登録はできません。

# ５．モックアップを作る

　フルフィルメントとのやりとりについては、[インテントのフルフィルメント](https://developers.google.com/assistant/smarthome/develop/process-intents )に概要があります。OAuth2.0 のやりとりはクリアしているとすると、次には action/devices.SYNC インテントがリクエストされます。要はjson形式のデータがPOSTされてくる。ドキュメントにはリクエストに対するレスポンス例もあるので、この例に沿った応答をまずは返すことにします。
　ただ、レスポンス例がだいぶ長いので、まずは単純なデバイス１つを扱うことにしました。[デバイス一覧](https://developers.google.com/assistant/smarthome/guides )を眺めて、シンプルに[OUTLETのレスポンス](https://developers.google.com/assistant/smarthome/guides/outlet#response )を使うことにします。
　requestIdはSYNCリクエスト内にある値をそのまま使えば良さそうです。サンプルを丸ごとecho文で返すことも考えなくはなかったのですが、さすがにそれはあんまりなので、何らかのデータ構造をハードコーディングし、それをjson_encode()関数に通すことにしました。
　サンプルとしては次のようなデータ構造というか、データ定義文になります。
```
$objRes = array(
   "requestId" => $intReqId,
   "payload"   => array(
      "agentUserId" => "userMockup",
      "devices" => array(
        array( // device one
         "id" => "t001",
         "type" => "action.devices.types.OUTLET",
         "traits" => array(
            "action.devices.traits.OnOff"
            ), // traits
         "name" => array( "name" => "virtual outlet" ),
         "willReportState" => true,
         "deviceInfo" => array(
            "manufacturer" => "HOME DEVICE",
            "model" => "mockup",
            "hwVersion" => "0.01",
            "swVersion" => "0.01"
            ) //deviceInfo
        ) // device one
     ) // devices
   )  // payload
);
```
  arrayのネストですが（個人的には）データ構造の持たせ方は見えてきます。
　あとは、リクエストのJSONからrequiestIdを$intReqIdとして抜き出す必要があります。また、リクエスト種別もJSONの中に埋め込まれているようなので、リクエストデータの簡単な処理は共通ルーチンとして前処理させてしまうことにします。
```
   $jsonBody = file_get_contents("php://input");

   $jsnIntent = json_decode($jsonBody);
   $intReqId = $jsnIntent->requestId;
   
   switch($jsnIntent->inputs[0]->intent){
     case "action.devices.SYNC":
       echo json_encode(array(……));
     break;
   }
```
　これでSYNCリクエストには応答できるはずですが、グーグルホームアプリの登録時にはエラーが出ます。ウェブサーバーにはSYNCに続いて、再度リクエストが届いていることがログから解ります。
```
$jsonBody = file_get_contents("php://input");
file_put_contents("req_google_json.txt", $jsonBody);
```
　などとして、リクエスト情報をダンプしてみると、QUERYがリクエストされたことが解りました。SYNCで制御下にあるデバイスの一覧を取得したあとに、続けてその状態についてリクエストしているようです。
　QUERYについてもOUTLETのサンプルを使い、ハードコーディングした情報を返すことにします。
```
$objRes = array(
   "requestId" => $intReqId, 
   "payload" => array(
    "devices" => array(
      "t001" => array(
          "status" => "SUCCESS",
          "online" => true,
          "on" => true
      )
    )
  )
 );
```
 SYNCレスポンスと合わせたソースコードはおおよそ次のようになりました。
```
<?php
  $jsonBody = file_get_contents("php://input");

  $jsnIntent = json_decode($jsonBody);
  $intReqId = $jsnIntent->requestId;
   
  $strRes="";
  switch($jsnIntent->inputs[0]->intent){
    case "action.devices.SYNC":
      $strRes = json_encode(array(…responce for SYNC…));
    break;

    case "action.devices.QUERY":
      $strRes = json_encode(array(…responce for QUERY…));
    break;
  }
  
  //Responce to Requester.
  echo $strRes;
?>
```
# ６．おわりに
　改めてグーグルホームアプリから登録操作をすると、無事に登録ができました。外部にサービスとして公開するためには、グーグルアカウントとの紐づけが必須になりますが、そこには触れていません。今はグーグルホームアプリとＡＰＩが動くサーバー間で完結させているため、触れようがないということもあります。ドキュメントを読み込んだら、もしかしたら実装方法がどこかにあるのかもしれませんが、今のところはあくまでも作成者限定の個人運用のみということになります。
　今回のモックアップで使ったデバイスはOUTLETなので、オン・オフ操作ができるものになるのですがEXECUTEは実装していないので当然動きません。また、デバイス定義がハードコーディングというのもあんまりといえばあんまりです。とりあえずデバイス操作については稿を改めたいと思います。