GoogleHomeからRESTを叩いて、実行結果を喋らせる[その3～5]

[GoogleHomeからRESTを叩いて、実行結果を喋らせるその[2]](https://qiita.com/sahksas/items/11944574ca3c24ba0193)
の続き。

# 記事一覧
 - [[0] Raspberrypiのセットアップ](https://qiita.com/sahksas/items/f35e8b96ca7a25e00a32#0-rasberrypi%E3%81%AE%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97)
 - [[1] IFTTTからGoogleAssistant⇒GoogleSpreadSheetでタスク名を書き込む](https://qiita.com/sahksas/items/f35e8b96ca7a25e00a32#1-ifttt%E3%81%8B%E3%82%89googleassistantgooglespreadsheet%E3%81%A7%E3%82%BF%E3%82%B9%E3%82%AF%E5%90%8D%E3%82%92%E6%9B%B8%E3%81%8D%E8%BE%BC%E3%82%80)
 - [[2] ローカルネットワークに繋いだRaspberrypiで[1]を受け取る](https://qiita.com/sahksas/items/11944574ca3c24ba0193#2-%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF%E3%81%AB%E7%B9%8B%E3%81%84%E3%81%A0raspberrypi%E3%81%A71%E3%82%92%E5%8F%97%E3%81%91%E5%8F%96%E3%82%8B)
 - [[3] Raspberrypiでそのまま[2]を叩く](https://qiita.com/sahksas/items/b7311b139048dd70b532#3-raspberrypi%E3%81%A7%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE2%E3%82%92%E5%8F%A9%E3%81%8F)
 - [[4] Raspberrypiで[3]の結果を受け取る](https://qiita.com/sahksas/items/b7311b139048dd70b532#4-raspberrypi%E3%81%A73%E3%81%AE%E7%B5%90%E6%9E%9C%E3%82%92%E5%8F%97%E3%81%91%E5%8F%96%E3%82%8B)
 - [[5] Raspberrypiで[4]の結果をGoogleHomeに喋らせる](https://qiita.com/sahksas/items/b7311b139048dd70b532#5-raspberrypi%E3%81%A74%E3%81%AE%E7%B5%90%E6%9E%9C%E3%82%92googlehome%E3%81%AB%E5%96%8B%E3%82%89%E3%81%9B%E3%82%8B)

# [3] Raspberrypiでそのまま[2]を叩く
```py:google_test/test.py
i = 1
for i in wks.col_values(1):
    if wks.cell(i,3).value != '': #C列(RobotDirectory)がブランクでなければ
        if wks.cell(i,5).value == '': #E列(start)がブランクなら
            wks.update_cell(i,5,datetime.datetime.today().strftime("%Y/%m/%d %H:%M:%S")) #E列に実行開始日時を入力
            robotdir = wks.cell(i,3).value #C列(RobotDirectory)を取得
            print(robotname)
```
前回がここまで。
GoogleSpreadSheetを参照して未実行の行からC列の値を取得した。

これを元にリクエストを送る。

```py
import requests
```
requestsをインポート。

```py
# リクエスト送信先の情報
roboserverURL = 'http://**.***.***.**:*****/'
username = 'admin'
password = '********'
```
今回ロボットを動かすので、それが動くサーバーのURL、username、passwordを予め指定。

```py
requestURL = roboserverURL+robotname+'.robot' #RobotDirectoryをリクエスト用のURLに充てる
print(requestURL) #リクエスト用URL表示
r = requests.get(requestURL, auth=(username, password)) #リクエスト実行
```
先程指定したURLや、[[2] ローカルネットワークに繋いだRaspberrypiで[1]を受け取る](https://qiita.com/sahksas/items/11944574ca3c24ba0193#2-%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF%E3%81%AB%E7%B9%8B%E3%81%84%E3%81%A0raspberrypi%E3%81%A71%E3%82%92%E5%8F%97%E3%81%91%E5%8F%96%E3%82%8B)で取得した値をくっつけてリクエスト用のURLを作る。
リクエストGETメソッドのinput内で文字の連結が出来なかった(？)ので先に作っておいた。
ユーザー認証情報も入れてGET実行。

```py
wks.update_cell(i,6,datetime.datetime.today().strftime("%Y/%m/%d %H:%M:%S")) #F列に終了日時を入れる
```
SpreadSheetのF列に終了日時を入れる。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/69848/ae160797-854b-76e8-4a1e-9c7ec2c94c86.png)

テスト。
E列に入っている値を一旦削除して未実行の状態にする。

![処理済～rapture_20180530190703.jpg](https://qiita-image-store.s3.amazonaws.com/0/69848/55d4980c-6df5-f24d-36db-4cae8b5f6d30.jpeg)

実行結果。
2つのロボットが実行された様子。

![rapture_20180530190910.jpg](https://qiita-image-store.s3.amazonaws.com/0/69848/f73464c6-035e-6955-8c21-2532fec29de4.jpeg)

メールを3通送信するロボット。
10分間の待機後メールを送信するロボット。どちらも正しく動いている。

# [4] Raspberrypiで[3]の結果を受け取る
```py
if r.status_code == 200: #実行が最後まで終了したなら
    print(r.text) #実行結果表示
if r.status_code != 200: #実行が途中でエラーとなったら
    print(r.status_code) #ステータスコード表示
```
```py
wks.update_cell(i,8,r.status_code) #G列にステータスコードを入れる
```

先のコードに、実行結果を取得する文と、そのステータスコードをSpreadSheetに書き込む文を挿入。

ステータスコードは成功が200、失敗がそれ以外となるので、分岐で完了結果かエラーコードを返すようにした。

![処理済～rapture_20180530192119.jpg](https://qiita-image-store.s3.amazonaws.com/0/69848/0b61e61f-1375-f0cc-e87b-1929ddfee77b.jpeg)

ロボットは正常に完了し、結果がXML形式で返ってきた。

メールを3通送信し1通送信毎にカウントしているので、
「NumberSend」には「3」。
「msg」にはロボットの最終ステップで作られる「NumberSend」を参照したメッセージが入った。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/69848/2928f511-9dd9-1965-b6b1-142bc18b9008.png)

SpreadSheetのF列～H列にも値が入った。
G列には予め入っていた関数が計算した実行時間が秒単位で入る。

# [5] Raspberrypiで[4]の結果をGoogleHomeに喋らせる
↑で取得した結果のXMLをパースして、google-home-notifierで喋らせる。

## 返ってきたXMLをパースして、喋らせるメッセージを準備
参考:[20.5. xml.etree.ElementTree — ElementTree XML API — Python 3.6.5 ドキュメント](https://docs.python.jp/3/library/xml.etree.elementtree.html#xpath-support)

```py
import xml.etree.ElementTree as ET
```
XPathをインポート。

```py
if r.status_code == 200: #実行が最後まで終了したなら
    root = ET.fromstring(r.text)
    msg = root.find(".//attribute[@name='msg']")
    print(msg.text)
```
先程の結果から、属性「msg」を持つ「attribute」タグを分析しテキストを取得。
find内の文はXMLの内容によって適宜変更する。

![処理済～rapture_20180531161850.jpg](https://qiita-image-store.s3.amazonaws.com/0/69848/f797bf46-c292-aa73-d473-a9e6854ad827.jpeg)

テスト。
XMLから結果のテキストのみが抽出された。

## google-home-notifierを使う準備
参考:[Raspberry PiからGoogle Homeを喋らせる - Qiita](https://qiita.com/ktetsuo/items/8c9cd5714e231aa6ae09)

```js:googlehome/speak.js
const googlehome = require('google-home-notifier');
const language = 'ja';
googlehome.device("Google-Home", language);
googlehome.ip("***.***.***.***", language);
googlehome.notify('こんにちは。私はグーグルホームです。', function(res) {
  console.log(res);
});
```

↑の参考に補足で、
・「device」だけではなく「ip」でも「language」を指定しなければならない。
・jsをUTF8で書くこと。
この2つに注意しなければ、日本語を喋ってくれない。

また、GoogleHomeのIPアドレスを確認する方法として、携帯アプリの設定画面から見ることが出来る。
と各所で書かれているのだが、iphone8及びiPhoneXでアプリをみると画面のレイアウトがバグっており確認できなかった。。

参考:[Google Homeの名前とIPアドレスを検出する方法 – 山本隆の開発日誌](https://www.gesource.jp/weblog/?p=8082)
この方法で調べた

```terminal
cd googlehome
node speak.js
```

テスト。
ドゥルン！の後に言葉を喋ってくれれば成功。

## [4]の実行結果を喋らせる
ようやく本命。
喋らせるメッセージをPythonからJavaScriptに渡して叩けばよい。

```py
import subprocess
```
subprocessをインポート

```py
robotname = wks.cell(i,2).value #B列(RobotName)を取得
```

実行したロボットの名前も喋ってほしいので、SpreadSheetからB列を取得する行も追加。

```py
if r.status_code == 200: #実行が最後まで終了したなら
    root = ET.fromstring(r.text)
    msg = root.find(".//attribute[@name='msg']")
    speakmsg = 'node /home/user/googlehome/speak.js '+robotname+'の実行が完了しました。'+msg.text
    subprocess.call(speakmsg, shell=True)
if r.status_code != 200: #実行が途中でエラーとなったら
    speakmsg = 'node /home/user/googlehome/speak.js '+robotname+'の実行が完了しました。'+'エラーが発生しました。'
    subprocess.call(speakmsg, shell=True)
```

subprocess.callでjsを叩く。jsは絶対パスで指定。
成功した場合は、結果のテキストを。
失敗した場合は、「エラーが発生しました。」を喋らせる。
ステータスコードで失敗理由の検討がつきそうなら、それを喋らせてもよいかも。

test.pyのコーディングは終わり

```py:google/test.py
# -*- coding: utf-8 -*-

import gspread
import datetime
import requests
import xml.etree.ElementTree as ET
import subprocess
from oauth2client.service_account import ServiceAccountCredentials

scope = ['https://spreadsheets.google.com/feeds',
         'https://www.googleapis.com/auth/drive']

credentials = ServiceAccountCredentials.from_json_keyfile_name('test-********.json', scope)
gc = gspread.authorize(credentials)
wks = gc.open('cue').sheet1

# リクエスト送信先の情報
roboserverURL = 'http://**.***.***.**:*****/'
username = 'admin'
password = '********'

i = 1
for i in wks.col_values(1):
    if wks.cell(i,3).value != '': #C列(RobotDirectory)がブランクでなければ
        if wks.cell(i,5).value == '': #E列(start)がブランクなら
            wks.update_cell(i,5,datetime.datetime.today().strftime("%Y/%m/%d %H:%M:%S")) #E列に実行開始日時を入力
            robotname = wks.cell(i,2).value #B列(RobotName)を取得
            robotdir = wks.cell(i,3).value #C列(RobotDirectory)を取得
            requestURL = roboserverURL+robotdir+'.robot' #RobotDirectoryをリクエスト用のURLに充てる
            print(requestURL) #リクエスト用URL表示
            r = requests.get(requestURL, auth=(username, password)) #リクエスト実行
            if r.status_code == 200: #実行が最後まで終了したなら
                root = ET.fromstring(r.text)
                msg = root.find(".//attribute[@name='msg']")
                speakmsg = 'node /home/pi/googlehome/speak.js '+robotname+'の実行が完了しました。'+msg.text
                subprocess.call(speakmsg, shell=True)
            if r.status_code != 200: #実行が途中でエラーとなったら
                speakmsg = 'node /home/pi/googlehome/speak.js '+robotname+'の実行が完了しました。'+'エラーが発生しました。'
                subprocess.call(speakmsg, shell=True)
            wks.update_cell(i,6,datetime.datetime.today().strftime("%Y/%m/%d %H:%M:%S")) #F列に終了日時を入れる
            wks.update_cell(i,8,r.status_code) #H列にステータスコードを入れる
```

#仕上げ
 - **[1] IFTTTからGoogleAssistant⇒GoogleSpreadSheetでタスク名を書き込む**
 - **[2] ローカルネットワークに繋いだRaspberrypiで[1]を受け取る**
 - **[3] Raspberrypiでそのまま[2]を叩く**
 - **[4] Raspberrypiで[3]の結果を受け取る**
 - **[5] Raspberrypiで[4]の結果をGoogleHomeに喋らせる**

が出来上がったが、これでは喋る度にtest.pyを実行しなければならない。

```terminal
cd google_test
watch -n10 'python3 test.py'
```

ので、RaspBerrypi側でtest.pyをぐるぐる回し続けることにした。


![処理済～rapture_20180601162542.jpg](https://qiita-image-store.s3.amazonaws.com/0/69848/fad14b63-f494-57d1-6552-479fe97addfc.jpeg)

10秒おきにtest.pyが実行されるようになった。
これでSpreadSheetに変更があり次第それを拾って処理を行ってくれるように。

## 結果
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">GoogleHomeに命令して大体自動で仕事させるようになどした<br>不労所得が近づく <a href="https://t.co/wq7kIY4T3f">pic.twitter.com/wq7kIY4T3f</a></p>&mdash; baku (@sahksas) <a href="https://twitter.com/sahksas/status/1002858221705486337?ref_src=twsrc%5Etfw">2018年6月2日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

「OK Google。実行予約 受付リスト作成」
できた！！

# まとめ
大分遠回りなやり方になったが、したいことは出来たので一先ず完了。
これまで作ったロボットの、今まで実行結果をメールやslack送信していた部分をテキストのアウトプットに変えなければならない。。

firebaseを使えば、もっといろいろなことがよりスマートに出来るそうだが、新しく覚えることが多くて時間がかかりそうだったのでこのような方法をとった。
次回はそちらに挑戦したい。


##未解決問題
**test.pyが動くたびにSpreadSheetにアクセスするので、APIリクエスト制限に引っ掛かりそう**
⇒GASでシート内の編集があった時にだけコマンドを叩くとか。できるのかな。
　シート内でのループもかなり怪しい。

**パラメータも指示してRESTを叩きたい**
⇒SpreadSheetへの入力部分をIFTTTでさぼっているので仕方ない。(GoogleAssistantトリガーで認識できるフレーズは1つ)
トリガー内容を変えれば文字列と数字を1つずつ認識出来るので、「実行予約 ロボット【n】番 【xx】さん」とかで一応パラメータ1つならいける。
けど使い勝手悪すぎて現実的じゃない。

**1つの指示で複数のロボットを実行したい**
⇒SpreadSheetに書きこむ際、1つのワードで複数行に跨って命令を入れる必要がある。
GoogleAsisstant⇒IFTTT⇒GoogleSpreadSheet内にまた何か噛ませなければならない。
GASや関数で解決出来るかもしれない。後々調べる。

**SpreadSheetが2000行を超えると追記入力出来ない(？と聞いた)**
⇒方法はありそうだが、どれもスマートじゃない。