1.はじめに
前回のOAuth 2.0 Server PHP と Google OAuth2.0 Playground を接続する でOAuth2.0サーバーが使えるようになったので、本稿ではデバイスのような振る舞いをするものを作って行こうと思います。正確にはデバイスを管理するスマートフォンアプリ的なものです。デバイスがあるかのような応答をさせようというものです。あくまでもテストとして。
流れとしては以下の通りです。
- アカウントリンクを作成する
- フルフィルメントを登録する
- モックアップを作る
- グーグルホームアプリに登録する
フルフィルメントは要するにAPIですが、とりあえず応答らしきものをするモックアップを作成し、デバイスとして登録されることを本稿の目標にします。
2.アカウントリンクを作成する
アカウントリンク実装についての概要はドキュメントで説明されています。アクションコンソールに移動し、まず新規プロジェクトを作成します。
- New project をクリックして、Project Nameに入力。language や countryも適当に。
- 作成したプロジェクトをクリック
- Quick setup をクリック
- Name your Smart Home actionをクリックし、Display name を設定する。
このDisplay nameはアシスタントアプリが読み上げに使います。 - Setup account linking をクリックし、クライアントID、クライアントシークレットを設定し、これまで作ってきたOAuth2.0サーバーの認証URI、トークンURIを設定します。それと、これが必要かどうかは今一つ解っていないのですが、Configure your client (optional)でScopesにbasicを設定します。
- 右上のSaveをクリックする
また、クライアントIDとクライアントシークレット、リダイレクトURIをリソースDBに登録しておきます。リダイレクトURIについてはアカウントリンクの実装内で触れられていますが、次のような形式になります。
https://oauth-redirect.googleusercontent.com/r/YOUR_PROJECT_ID
3.フルフィルメントを登録する
アカウントリンクの作成に続いて、フルフィルメントも設定します。
- 左ペーンにあるメニューのActionsをクリックし、Fullfilment URLに呼び出し用のURIを設定します
https://your.domain/gsm_api.php
- 右上のSaveをクリックする
4.グーグルホームアプリに登録する
作成したプロジェクトをデバイスとしてグーグルホームアプリに登録します。グーグルホームアプリを起動して、設定→デバイスの追加を選び、検索窓で登録したDISPLAY NAMEを入力します。
[TEST] DISPLAY NAMEのような表示がされると思うので、タッチすれば登録される……のですが、今はフルフィルメントに登録したURIの実体が無いのでエラーになって登録はできません。
5.モックアップを作る
フルフィルメントとのやりとりについては、インテントのフルフィルメントに概要があります。OAuth2.0 のやりとりはクリアしているとすると、次には action/devices.SYNC インテントがリクエストされます。要はjson形式のデータがPOSTされてくる。ドキュメントにはリクエストに対するレスポンス例もあるので、この例に沿った応答をまずは返すことにします。
ただ、レスポンス例がだいぶ長いので、まずは単純なデバイス1つを扱うことにしました。デバイス一覧を眺めて、シンプルにOUTLETのレスポンスを使うことにします。
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;
?>
6.おわりに
改めてグーグルホームアプリから登録操作をすると、無事に登録ができました。外部にサービスとして公開するためには、グーグルアカウントとの紐づけが必須になりますが、そこには触れていません。今はグーグルホームアプリとAPIが動くサーバー間で完結させているため、触れようがないということもあります。ドキュメントを読み込んだら、もしかしたら実装方法がどこかにあるのかもしれませんが、今のところはあくまでも作成者限定の個人運用のみということになります。
今回のモックアップで使ったデバイスはOUTLETなので、オン・オフ操作ができるものになるのですがEXECUTEは実装していないので当然動きません。また、デバイス定義がハードコーディングというのもあんまりといえばあんまりです。とりあえずデバイス操作については稿を改めたいと思います。