AzureFunctions
LINEmessagingAPI
GoogleHome
google-home-notifier

LINEから家のGoogleHomeに好きな言葉を喋らせる

前回(LINEをトリガーに家のGoogleHomeを喋らせる - Qiita)で、LINEから何かメッセージを受信したら家のGoogleHomeを喋らせることができた。しかし、どんなメッセージを受信しても同じ言葉しか喋らないため、今度はちゃんとLINEで送信したメッセージを喋らせることにした。

設計

LINE Messaging API - Webhookイベントオブジェクト によると、Webhookで下記のようなJSONデータが送られるようだ。

「Webhookイベントオブジェクトの例」より
{
  "events": [
    {
      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
      "type": "message",
      "timestamp": 1462629479859,
      "source": {
        "type": "user",
        "userId": "U4af4980629..."
      },
      "message": {
        "id": "325708",
        "type": "text",
        "text": "Hello, world"
      }
    },
    {
      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
      "type": "follow",
      "timestamp": 1462629479859,
      "source": {
        "type": "user",
        "userId": "U4af4980629..."
      }
    }
  ]
}

このJSONデータから、events>message>textと構造体をたどれば、LINEで送信したテキストが取得できそうである。
どこかにこのWebhookを受け取るWebアプリを作ってJSONを解析すればよい。巷ではこういった用途だと「heroku」なるものがよく使われているようだが、今回はAzureの勉強も兼ねて「Azure Functions」で作成することにした。

最終的な構成は以下のようになった。

line-azurefunctions.001_s.png

前回(LINEをトリガーに家のGoogleHomeを喋らせる)のIFTTTがAzure Functionsに置き換わっているだけ。

実装

beebotte 設定

talkerという名前のチャンネルを作成し、その中にmessagetextというリソースを作成した。

beebotte設定

Azure Functions

AzureはMicrosoftが提供しているクラウドサービスで、Azure Functionsは「サーバレスプログラム」と言われており、要するに関数を実行するAPIを簡単に作れるサービスである。
Azureに登録すると、3ヶ月無料で試用できるサブスクリプションがもらえるので、気軽に試せる。筆者は無料期間は終わっていたので従量課金のサブスクリプションを契約したが、この記事で作ったアプリを3週間ほど使って課金額は30円程度だった。

Python関数の作成

  • Azureのダッシュボードから「新規」をクリックし、Function Appを選択し、「作成」。
    Functions App を選択

  • Function Appの情報を入力し、「作成」
    Function Appの情報を入力

  • 「デプロイしています…」で少し時間がかかるが、待つと作成が完了する。

  • 「関数」の横の「+」をクリックする。右側にクイックスタートが出てくるが、今回はPythonの関数を作るので「カスタム関数を作成する」をクリックする。
    関数の追加

  • 「HTTPトリガー」の「Python」を選択する。
    HTTPトリガーのPythonを選択

  • 関数の名前を入力して「作成」をクリックする。
    関数の名前を入力

  • 最初のサンプルコードが表示されている。「実行」で関数を実行してテストできる。

Functionsコード編集画面

  • 右上のほうにある「関数のURLを取得」で、この関数のURLを表示できる。後にLINEbotに設定するので、控えておく。

beebotte用Pythonモジュールのインストール

Pythonでbeebotteを操作するのに、REST APIを使って操作してもよかったが、Beebotte Python SDK というのがあることを知ったので、これを使うことにした。
Azure FunctionsのPython環境にはこのモジュールはインストールされていなかったので、まずはモジュールをインストールするところから。
Function Appの環境設定は、KuduというWeb上のコンソールツールを使って行う。
Function Appのページから「プラットフォーム機能」のタブの中の「高度なツール(Kudu)」をクリックすると、Kuduが起動できる。
スクリーンショット 2018-01-31 0.07.10.png

  • Python3.6をインストール(2018/04/08 追記)

Kuduを使ってpipを使用してモジュールをインストールしようとしたが、単にpipとやろうとしたら、パーミッションがないと怒られたりしてなかなかハマった。
最終的に https://stackoverflow.com/questions/43970307/azure-functions-installing-python-modules-and-extensions-on-consumption-plan/43984890 あたりを参考に、virtualenvを使う下記の方法でできた。
Kuduの上のメニューからDebug ConsoleのCMDで、コマンドプロンプトが表示できる。

  • 関数のディレクトリに移動
>cd D:\home\site\wwwroot\HttpTriggerPython31
  • 仮想環境を作成、有効化
>python -m virtualenv env
  • しばらく時間がかかるが、待っていると完了するので完了したら仮想環境を有効化する。
>cd env\Scripts
>activate.bat
  • pip更新とbeebotteモジュールインストール
>python -m pip install -U pip
>python -m pip install beebotte

コード(Python)

コード編集画面で、コードを編集する。
冒頭で、モジュールをインストールしたvirtualenvのパスを追加している。これをしないと「モジュールが見つかりません」となる。環境の設定でPYTHONPATHを変更する方法もあるようだが、簡単のためコード内でパスを追加した。

# pythonのモジュール検索パスに、beebotteインストールしたパスを追加
import sys, os.path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname( __file__ ), 'env/Lib/site-packages')))

import os
import json
from beebotte import *

postreqdata = json.loads(open(os.environ['req']).read())
response = open(os.environ['res'], 'w')
messagetext = postreqdata['events'][0]['message']['text']  # JSONからメッセージのテキストを抽出
response.write(messagetext.encode('utf-8'))
print(messagetext.encode('utf-8'))
#print(postreqdata)

_hostname = 'api.beebotte.com'
_token = 'token_###################' # beebotteのトークンを記載
bbt = BBT(token = _token, hostname = _hostname)
bbt.write('talker', 'messagetext', messagetext)

response.close()

LINEbot 設定

Webhook URLに、作成したAzure FunctionsのURLを設定。

LINEbot 設定

Raspberry Pi 側プログラム(Node.js)

前回とほぼ変わらないが、リソース「messagetext」に書かれたテキストを読み上げるようにしている。

main.js
const googlehome = require('google-home-notifier');
const mqtt = require('mqtt');

const language = 'ja';
googlehome.device("Google-Home", language);
googlehome.ip("192.168.xxx.xxx");  // GoogleHomeのIPアドレスを記載

const client = mqtt.connect('mqtt://mqtt.beebotte.com',
  {username: 'token:token_###############', password: ''}  // beebotteのトークンを記載
);

client.on('connect', function() {
  client.subscribe('talker/messagetext');  // 
});

client.on('message', function(topic, message) {
  console.log(message.toString());
  const json = JSON.parse(message);
  console.log(json.data);
  googlehome.notify(json.data, function(res) {
    console.log(res);
  });
});

完成形

動かしたときの動画がこちら。

感想

  • Azure Functionsを初めて使ったが、WebAPIのためのサーバーを用意したりApatchを動かしたりといったことが不要なので、こういう用途には良いと感じた。
  • IFTTTのときよりも応答に時間がかかるようになってしまった。Azure FunctionsはHTTPリクエストが来た時点でモジュールの読み込みなどから動くため、時間がかかるらしい。応答速度を早くしたければ、モジュールのロードなどは終わった状態で、常時リクエストを受け付けるようなWebAPIを作成したりする必要がある。
  • 3週間ほど家に帰るときにGoogleHomeに喋らせていたら、1歳半の娘がGoogleHomeを指して「おとーしゃん」と言うようになった。