IT関係」カテゴリーアーカイブ

Google Homeでごみ収集日botを動かしました

物欲の秋ですね。
Google Homeを買ったので遊んでみました。

Google Homeとは

Googleが発売したスマートスピーカーです。
スマートスピーカーとは、話しかけることで、インターネットを通じてさまざまな機器と連携したり、ニュースや天気予報なんかを教えてくれる機器の総称です。
競合としては、AmazonのEchoやLINEのClovaなんかが挙げられます。
ちなみに今のところシェアはEchoが1位です。

Google Homeで出来ること

こちらのビデオをどうぞ。

Google HomeにはGoogleアシスタントと呼ばれるAIが搭載されていて、様々な問いかけに答えてくれます。

個人的にはリラックスする音楽かけて〜でいい感じに流してくれるのが気に入りました。

今後、連携サービスが増えていくみたいですね。
http://japanese.engadget.com/2017/10/04/ai-google-home/

それと、スマホのGoogleアシスタントやSiriは結構声を張らないと認識してくれない印象なのですが、こいつは割とボソボソ喋っても認識してくれます。
日常に溶け込むと言う点でこれはありがたかったです。

api.aiとActions on Googleで遊んでみたよ

api.ai : https://api.ai/
Actions on Google : https://developers.google.com/actions/

api.aiを使えば、Google Homeアプリが手軽に作れると聞いて、触ってみました。
Google Home(Google アシスタント)との接続にはActions on Googleを使用します。

この連携のはじめの一歩には、こちらの記事を参考にさせて頂きました。
Actions on Googleでapi.aiを使ってGoogle Homeに何か言わせてみる – Qiita

api.aiは自然言語処理をいい感じにやってくれてAPI化してくれるサービスです。
Googleが買収したので、Googleアシスタントとの連携も簡単。
プロジェクトを作るとGCP/Firebaseにも同名でプロジェクトが作られます。

今回作るのは、

「今日は何ゴミの日?」
今日は燃えるゴミの日です。

みたいな会話が出来るbotです。

まずapi.aiについて調べました。
大きくEntitiesとIntentsと呼ばれる2つの概念と、外部サービスとのIntegrationが存在します。

Entities

AI用のデータベースみたいなものです。

@genki-naiは、体力、精神、仕事、子育て
みたいなヒモ付ができます。
ここで定義した単語を次のIntentsでのトリガーに使用することができます。

Intents

トリガーとレスポンスを定義します。

トリガーの例 : @genki-naiがつらい
レスポンスの例 : げんき

あらかじめ定義したいくつかの単語をフックして特定の返答をする例です。

予め、「今日」→「日付」とか、「浜田山」→「緯度経度」
みたいなEntitiyが定義されていて、言語解析に使用できます。

try

Entitiesで定義した人生という単語と、子育てや仕事などを紐付けて返答が返ってきました。

JSONで見ると、このように解析されています。

{
  "id": "e84bec73-b2da-4485-868c-129d81b311a5",
  "timestamp": "2017-10-09T04:05:25.277Z",
  "lang": "ja",
  "result": {
    "source": "agent",
    "resolvedQuery": "子育てがつらい",
    "action": "",
    "actionIncomplete": false,
    "parameters": {
      "genki-nai": "人生"
    },
    "contexts": [],
    "metadata": {
      "intentId": "fa350d49-7a1c-4191-844b-1d4f9ceff956",
      "webhookUsed": "false",
      "webhookForSlotFillingUsed": "false",
      "intentName": "元気出して"
    },
    "fulfillment": {
      "speech": "人生 がつらいだって? 元気出して!",
      "messages": [
        {
          "type": 0,
          "id": "3dc30f75-1ba6-4ed2-b061-aea17e777844",
          "speech": "人生 がつらいだって? 元気出して!"
        }
      ]
    },
    "score": 1
  },
  "status": {
    "code": 200,
    "errorType": "success"
  },
  "sessionId": "4d5fe34c-59d7-49f8-808a-8e6a77418fae"
}

発せられた特定のワードをフックして動作するbotのようなものが作れそうです。

Integrations

入力と出力に外部サービスが使えます。
今回のGoogle Assistantの他にSlackやFacebook Messengerなどとも連携できます。

ゴミの日bot

本題に戻って、ゴミの日botです。

今日は 何ゴミの日?

僕の住んでいる住所に限定するのであれば、今日と言う単語から曜日を拾ってしまえば単純なマッピングから何ゴミの日か返せばよさそうです。

そう思って調べたところ、「今日は」から日時は取れるけど曜日が取れませんでした。
よくよく見てみると、条件分岐も1つのIntentでの定義はできないようです。
(複数のIntentを組み合わせればできる気もします。)

api.aiはシンプルに言語解析のハブのようなイメージなのだなーと改めて理解しました。

そこで、外部サービスとの連携を試します。

Fulfillmentを使って外部サービスと連携する

Fulfillment  |  API.AI

ようはWebhookです。
Intentsで解析した結果をJsonで外部サービスに渡すことができます。
今のところ、現在地の天気を取得するような高度なことをしたければこれを使う想定のようです。

今回はこんな構成にしてみました。

Actions on Google -> api.ai -> Firebase functions

今の日時の自分の地域のゴミ予定を返すだけのAPIを作りました。
https://github.com/ymegane/suginami-garbage-date/blob/master/functions/index.js

これで、Google Assistantから要求を受けたら、api.aiでリクエストを受けて、
Webhookで今日と明日のごみ収集予定を返すことができました。
Actions on Google側の設定も多少必要ですが、今回は端折ります。

動作イメージ(Actions on Google上のシミュレーターで確認)

Google Home上での確認

振り返ってみると、これくらいならapi.aiは不要な感じがしますが、Slackなんかとも連携するのであれば便利そうです。


まとめ

api.aiとActions on Googleを使うと、ユーザーの文脈をいい感じに取得できます。

  • デバイスの位置情報から適切な地域のゴミ予定を返す
  • ログインさせて場所を設定させる
  • 火曜日は?みたいな自然な問いかけに対応する

などなど、色々可能性はありそうです。

Actions on Googleは今回はDRAFT機能を使ってテストアプリとして動作させました。
正式なアプリとして配布するには申請が必要です。
情報が少なすぎて、まだ良くわかっていません。
申請時に、他のアプリとトリガーワードが被ってたりするとまずそうな雰囲気を感じました。
(つよいトリガーワードをとったベンダーが勝つみたいなのありそうです。)

取り急ぎ、奥さんにやや喜ばれたので成功ということにしておきます。

Essential Phone PH-1を買ったのでステータスバーを見てみる

ずいぶん悩んだんですが、Essential Phone買ってみました。

https://www.essential.com/jp

数日使ってみて、一番もやっとポイントであるステータスバーについてまとめました。
Androiderのみんな、Pixel2日本で発売されるといいな!こいつのことは忘れよう!

Essential社、そしてEssential Phoneについて

正式には Essential Phone PH-1です。

Essential社についてすごく簡単に説明すると、Googleの元祖Android責任者だったAndy RubinがGoogleを辞めた後に立ち上げた会社です。
発売前からすでに3億ドルの資金調達に成功したりして注目度半端ないです。

そんなEssential社の製品第一弾がこのPH-1と言うわけです。
ベゼルレスディスプレイ(画面端が極端に小さい)、アルミより硬いチタン合金のボディ、そして背面にアタッチメント方式で様々なモジュールを追加できる点なんかが特徴です。

ベゼルレスが活用されるのはWhiteListのアプリだけ

PH-1の上部には大きなフロントカメラがディスプレイの上に鎮座しています。
さて、アプリから見た時にこの領域はどのように扱われるのでしょうか。

答えは、すべてステータスバーとして扱われます。
標準的なAndroidではステータスバー高は24dpですが、PH-1では約53dpになります。

標準的なサイズのステータスバー

PH-1の開発者設定

また、Googleアプリを中心に、初期インストールアプリについてはこの大きなステータスバー領域に対応した表示が行われます。
一般のアプリの場合は、単に黒いステータスバーになります。

Google Play

Twitterアプリのスクリーンショット

Twitter

Androidでは5.0からステータスバー色をアプリごとに定義できるようになっていて、現在では独自のテーマカラーを指定するのが一般的ですが、PH-1ではこの指定は無視されます。

これは、独自のAPIやPermissionで実現しているのではなく、独自のWhiteListで管理されています。

また、Google Playでアプリとして配布されており、要望に応じて随時アップデートしていくようです。

なぜこのような仕様なのか

独自に定義した53dpのステータスバー領域が曲者です。

Material Design に照らし合わせてもステータスバーのサイズは24dpが標準です。
また、Android Framework APIとしてステータスバーの高さが定義されていないため、取得には少し工夫が必要です。

標準的なUIテンプレートに沿っている場合、このステータスバー高は意識する必要はありません。

意識するのは、例えばGoogle Photosのようにスクロール時にステータスバーにもコンテンツを描画し、かつToolbarも表示する場合です。
僕の知る限り、これを実現するにはfitSystemWindowなど標準の仕組みでは不十分で、ステータスバー分の領域を含んだ、通常より大きいToolbarを、フルスクリーンの画面に表示するしか実現方法がありません。
この時、ステータスバー分の高さを24dpなど固定値で指定してしまっていると、PH-1ではレイアウトが崩れてしまうのです。
後述する解決策を適用するとこの制限を回避できますが、この問題も顕在化してしまいます。

ステータスバーが使えないために、通常より大きな(しかしPH-1には足りない)ツールバーが表示されてしまう例。(Feedly)

おそらくEssentialは、影響のあるアプリが多かったため、安全策をとったのだと思います。また、Essentialとしては、現時点でこのアプローチから方針を変える気はないようです。

アプリ開発者としては、初期許可アプリのWhiteListと並行して、独自のPermissionを持つアプリは描画を許可するような作りが自然だったと思いますね・・。

ユーザーとしての解決策

このWhiteListは、Secure Settingsとして保持されています。
これを変更することで、任意のアプリにステータスバーへの描画を許可することが出来ます。
Secure settingsは通常のアプリでの変更は許可されていませんが、SDKツールであるADBを経由して特定のアプリに変更の許可を与えることが出来ます。

PH-1の場合は、 ESSENTIAL_LAYOUT_WHITELISTがその設定値にあたり、すでにこれを編集するアプリを作った開発者もいます。

1ユーザーとしては、今のところこの設定値を自分で編集することで大部分のアプリでステータスバーが使えるようになり、ある程度のモヤット感は払拭できます。

WhiteListに追加したTwitter

それでも、先程のFeedlyのようにアプリの実装に依存して、このようになることもあります。

Feedlyで強制的にステータスバーへの描画を許可してしまうとレウアウトが崩れる

SlackもDrawerがめりこむ。

開発者としてできること

  • ステータスバーの値は固定値で指定しない。
  • ステータスバーの影響がなく、PH-1でもいい感じに表示させたければ、WhiteListへの追加をEssentialに依頼する。

以上の2点です。重ね重ね、アプリの実装ですべてが解決できる手段を用意して欲しいです。

PH-1に思うこと

PH-2があるのなら、恐らくフロントカメラはめり込ませないのではないでしょうか。
現在のAndroid Frameworkではこのフロントカメラのめり込みをサポートできる十分な用意がありません。
将来のAndroidが何か対策を講じないかぎり、PH-2はGalaxyS8のような一般的なベゼルレスに落ち着くように思います。
PH-1のアプローチは、ユーザーにとっても開発者にとっても全く良い点がありません。ただの見掛け倒しです。

もっと言うと、すでに魅力的なハードウェアだけで勝負ができる時代は終わりました。
iPhone XであれPixel2であれ、その上で動いているアプリやクラウドサービスの受け皿でしかありません。いかにサービスを快適に使えるハードウェアかという視点しか一般ユーザーには意識されていません。

PH-1には独自のサービスなどは提供されておらず、OSも標準的なAndroid 7.1です。(Status barの件を除いて)
であれば、Pixel2に対して有利な点って何でしょう?(Pixel2はまだ発表されてもいないけど。
この視点で言えば、Nextbit Robinの方が幾分、魅力的だったと思います。
結果は残念でしたが。


相当にディスっておいた後になんですが、僕は昔からのハードウェア好きなので、このあたりのことを考えつつも、PH-1を買いました。このドタバタも楽しんでいます。
性能はすばらしく、ディスプレイも綺麗です。Android 8.0ももうすぐ対応してくれるようです。
開発者としてはPixel2も手に入れないと来年以降の開発が大変そうです😇

[追記] ステータスバーをコード上で取得するとどうなるの?

Statusbarのサイズ(高さ)を取得する。 | Qiita

override fun onWindowFocusChanged(hasFocus: Boolean) {
    super.onWindowFocusChanged(hasFocus)

    Log.v("MainActivity", "statusBar height=${getStatusBarHeight(this)}")
}

fun getStatusBarHeight(activity: Activity): Int {
   val rect = Rect()
   val window = activity.window
   window.decorView.getWindowVisibleDisplayFrame(rect)
   return rect.top
}

結果

Emulator(XXHDPI) : 72
PH-1 : 159
PH-1(WHITELIST) : 159

動的に取っていると、WHITELISTに入れてないアプリの場合、よりデザインが崩れそうだね!