LINEでRaspberry PiにインストールしたAlexaを自由にしゃべらせる

#本記事の内容
- Raspberry PiにインストールしたAlexaを遠隔で自由にしゃべらせたいと思い、LINE経由でAlexaをしゃべらせることが出来るシステムを構築しました。

まず構成図は以下の通りです。
![画像2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/3bddc447-e3d4-ffe0-6db2-3d54420b9165.png)


Alexaに指示を出すアプリとしてLINEを活用しました。システムは以下のように連携しています。

- LINEで送ったメッセージが、LINEプラットフォームのLINE Messaging API経由で、Google Cloud Functionに連携される。

- Google Cloud Functionは、受け取ったデータの中からテキストデータのみをGoogleのNoSQL DBであるCloud Firestoreに登録する。
- Raspberry Piで稼働しているPythonアプリが、Cloud Firestoreのデータ更新を監視しており、データ更新時にそのデータをCloud Firestoreから取得する。

- Pythonアプリが取得したメッセージでAlexaの音声再生スクリプトを実行し、AlexaをLINEで指定したメッセージでしゃべらせる。

LINE Messaging APIから連携されるWebhookを受け取る方法として、Cloud Functions for Firebaseで作成したアプリを利用することも可能ですが、FirabaseのCloud Functionの開発言語はJavaScriptだけで、Pythonが利用できないので、Google Cloud PlatformのCloudFunctionを利用することにしました。

本記事ではこれらのシステム構築に必要なことをまとめました。

#実施環境
- ローカルPC環境
	- Windows 10
	- TeraTarm Version 4.9.4

- ラズベリーパイ
	- Raspberry Pi 2 Model B
	- PLANEX 無線LAN子機 GW-USNANO2A ※Raspberry Pi 2はWi-Fi通信モジュールがないため、無線LAN子機をUSBに接続しWi-Fi通信を行う。
	- 家庭内の無線LANネットワーク環境にて実施
	- 音声認識マイク
		- [MM-MC23](https://www.amazon.co.jp/gp/product/B007WSKGIE/ref=ppx_yo_dt_b_asin_title_o06_s00?ie=UTF8&th=1)
	- マイクをUSBに接続する変換機
		- [AU-MMSA](https://www.amazon.co.jp/gp/product/B00IRVQ0F8/ref=ppx_yo_dt_b_asin_title_o06_s00?ie=UTF8&psc=1)
	- スピーカ
		- [SP-PBLV2-BK](https://www.amazon.co.jp/gp/product/B07XF2SRSS/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&th=1)

	- Python 3.9.7
	- Alexa Voice Service (AVS) Device SDK 1.24.0
	- Snowboy 1.3.0

- クラウド環境
	- LINE Messaging API
	- Google Cloud Platform(以降、GCPと略す)
		- Cloud Function
	- Google Firebase
		- Cloud Firestore


![alexa_01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/13c42cc6-bd50-1b40-c1ad-3b467ff0fc83.jpeg)


#手順の流れ
- 1.Cloud Firestoreの構築
	- 1.1.プロジェクトの作成
	- 1.2.Cloud Firestoreの設定
	- 1.3.認証キーの取得
- 2.Cloud Functionの構築
	- 2.1.Cloud Functionの有効化
	- 2.2.関数の作成
- 3.LINE Messaging APIの構築
	- 3.1.LINE Messaging API用のLINEアカウント作成とWebhook設定
- 4.Raspberry Piの設定
	- 4.1.TeraTarmでsshログインする
	- 4.2.Firebase環境の構築
	- 4.3.Cloud Storeへのテスト接続
	- 4.3.Pythonアプリの配置
- 5.動作確認
	- 5.1.Alexaの起動
	- 5.2.Pythonアプリの起動
	- 5.3.LINEでテスト

## 1.Cloud Firestoreの構築
### 1.1.プロジェクトの作成

[Google Firebaseのコンソール](https://console.firebase.google.com/u/0/?pli=1)にログインし、`プロジェクトを作成`をクリックする。
![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/74c064ef-74a2-416b-992d-76716982bcfb.png)

`プロジェクト名`を入力し、規約を確認し同意のチェックをクリックし、`続行`をクリックする。
![02_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/ddcdfa28-21a9-fd86-92c2-6521f7f412ea.png)

Google アナリティクスの設定画面が出るので必要に応じて有効化する。
私は使わないため有効にするのチェックを外し、`プロジェクトを作成`をクリック。
![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/a0b2098e-e2cc-4e50-4323-5789bd303267.png)

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/9dbb3274-4c29-43fc-5b54-a14e6400c6bb.png)

「新しいプロジェクトの準備ができました」のメッセージが出るので、`続行`をクリックする。
![05_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/db70d18a-00d8-f9c6-01ef-765871b43e56.png)


### 1.2.Cloud Firestoreの設定

左のメニューから`Firestore Database`をクリックし、`データベースの作成`をクリックする。
![50_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/006bc577-9337-c4ed-f60b-2270974eda99.png)

`本番環境モードで開始する`をクリックし、`次へ`をクリックする。
![51_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/387a9a05-68ae-d950-2022-0f36338582cf.png)

ロケーションに変更の必要がなければそのままの設定で、`有効にする`をクリックする。
![52_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/469dcb70-332e-6e5e-e70e-38c9575d2d3c.png)

![53_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/f8161ff1-b225-b2a5-a644-f71e375d447b.png)

`コレクションを開始`をクリックする。
![54_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/d790e680-2153-7bf3-8fd7-10fb7948973e.png)

コレクションIDを入力し、`次へ`をクリックする。※ここの値はPythonアプリで利用するのでメモしておく。
![55_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/ec2ab8a8-cfd3-7029-5393-68a51ab69bf8.png)

ドキュメントIDのセルにある`自動ID`をクリックする。
![56_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/815e5634-6269-3344-291c-8471aa283869.png)

番号が払い出されるので、`保存`をクリックする。
![57_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/2b25e1c1-99c2-1f83-d153-2b9f4e8310f9.png)

![58_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/cb25466d-fa13-db38-f74a-2514a85ee516.png)


### 1.3.認証キーの取得

`プロジェクトの概要の隣の設定ボタン(歯車) > プロジェクトの設定`をクリック
![key_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/89d7be60-cc59-9aa3-1f5d-03816b766c89.png)
![key_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/ed4c997d-84eb-f188-6f9a-abd1b6146000.png)

プロジェクトの設定上部のタブの`サービスアカウント`をクリックし、Admin SDK 構成スニペットを`Python`に変更し、`新しい秘密鍵の生成`をクリックする。
![key_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/c39697a2-c4b2-7161-efe2-3ed27d0cf517.png)

`キーを生成`をクリックし、PCに保存する。
![key_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/04022746-caf4-1e96-af77-303742bcd049.png)

ここで取得した秘密鍵はラズパイからCloud Firestoreにアクセスする際に利用する。

## 2.Cloud Functionの構築
### 2.1.Cloud Functionの有効化
前提としてすでに、GCPにプロジェクトを作成済みであること。
もし未作成であれば、こちらの記事を参考に作成してください。
[ラズパイ連携に向けたGoogleSheetsAPIにおけるサービスアカウントの作成](https://qiita.com/kanamekun/items/abddb5c65d7a63d9935a)

GCPのコンソールを開いて、左のメニューから`Cloud Function`をクリックします。
![gcp_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/df860fe7-00aa-2904-1c95-af27e729e669.png)

課金設定がまだの場合、`課金を有効化する`をクリックする。
![gcp_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/85b1eab7-8f4d-599c-d099-63fb19ae9b6c.png)

請求先アカウントを選択し、`アカウントを設定`をクリックする。
![gcp_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/c1ed1267-8313-f3a0-0aae-c8c48bb0b0ba.png)
![gcp_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/fba50fad-3570-a55f-953d-c1a2565625db.png)

これでCloud Functionを利用する準備が整った。
![gcp_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/d489b556-458f-70ab-6955-b6a84527cc6e.png)


### 2.2.関数の作成

`関数を作成`をクリックする。
![gcp_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/d489b556-458f-70ab-6955-b6a84527cc6e.png)

関数名、リージョン、トリガーのタイプ(HTTP)、認証(未認証の呼び出しを許可)を設定し、次に進む。
![gcp_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/538173c6-09ae-36c4-ae6c-675c6afafd7d.png)
![gcp_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/b3c6fd39-399c-6039-5ac1-20fda8cf858d.png)

ここで表示されるURLは、LINEのWebhook設定で利用するのでメモしておくこと。

※本当であれば、認証ありでやりたいのですが、LINE Messagin APIのWebhookの仕組みにCloud Functionの認証を組み込めないので、未認証とします。

ランタイムの内容はデフォルトで問題なければそのまま進み、画面下部の`次へ`をクリックする。
![gcp_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/710ca225-740e-c0ea-9a75-9f144f371336.png)

メッセージでCloud Build APIの有効化を促されたので、`APIを有効にする`をクリックして有効化する。
![gcp_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/32a2b5bb-0062-e659-f99d-d1452649a82c.png)
![gcp_20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/7d2c7084-e54a-720e-f77e-f701f456a07f.png)
![gcp_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/b3f941a1-f5ea-ebc7-20c2-6d06d503b457.png)

関数の編集画面に戻り、ランタイムを`Python 3.9`に変更し、エントリポイントを`main()`に変更する。
main.pyの関数には、LINEのWebhook応答用に暫定的に以下のコードを入力する。

```python:main.py
def main(request):
    return {"statusCode": 200}
```

左側のrequirements.txtには以下の値を記載しておく。

	# Function dependencies, for example:
	# package>=version
	line-bot-sdk
	firebase-admin

左側の`＋`ボタンを押下し、1.3で取得した認証キーファイルと同じものを作成しておく。
ファイル名は、serviceAccountKey.jsonしておく。

	{
	  "type": "service_account",
	  "project_id": "",
	  "private_key_id": "",
	  "private_key": "",
	  "client_email": "",
	  "client_id": "",
	  "auth_uri": "",
	  "token_uri": "",
	  "auth_provider_x509_cert_url": "",
	  "client_x509_cert_url": ""
	}

変更後の画面はこのような形になるので、最後に`デプロイ`をクリックする。
![gcp_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/874ac182-56e0-e35b-9e27-1a2d9e53a68d.png)


★★ 3.1の手順最後にあるLINEのWebhookの検証が完了したら、main.pyは以下の本番用のソースコードに差し替えること。 ★★

```python:main.py
import base64
import hashlib
import hmac
import os

import firebase_admin
from firebase_admin import firestore
from firebase_admin import credentials

from flask import abort

from linebot import (
    LineBotApi, WebhookParser
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage
)


def main(request):
    channel_secret = os.environ.get('LINE_ACOUNT_SECRET')
    channel_access_token = os.environ.get('LINE_ACCOUNT_ACCESS_TOKEN')

    line_bot_api = LineBotApi(channel_access_token)
    parser = WebhookParser(channel_secret)

    # Line Message APIのリクエスト検証
    body = request.get_data(as_text=True)
    hash = hmac.new(channel_secret.encode('utf-8'), body.encode('utf-8'), hashlib.sha256).digest()
    signature = base64.b64encode(hash).decode()
    if signature != request.headers['X_LINE_SIGNATURE']:
        return abort(405)

    try:
        events = parser.parse(body, signature)
    except InvalidSignatureError:
        return abort(405)

    # テキストメッセージがWebhookされたときだけ処理を行う。
    for event in events:
        if not isinstance(event, MessageEvent):
            continue
        if not isinstance(event.message, TextMessage):
            continue

        print(f'event message  : {event.message} , type : {type(event.message)}')
        print(f'event message text : {event.message.text}')

        # Firebase 初期化
        if not firebase_admin._apps:
            cred = credentials.Certificate("./serviceAccountKey.json")
            firebase_admin.initialize_app(cred)

        db = firestore.client()

        # テキストメッセージの登録
        doc_ref = db.collection(u'line-message')
        doc_ref.add({
            u'text': f'{event.message.text}'
        })

        # 応答メッセージを返却
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text=event.message.text + " をお家ちゃんが読み上げます")
        )
    return {"statusCode": 200}

```
また、Google Cloud Founctionのランタイム環境変数に以下を設定する。
LINE_ACOUNT_SECRET：LINE Messageing APIのチャネルシークレット
LINE_ACCOUNT_ACCESS_TOKEN：LINE Messageing APIのチャネルアクセストークン

そうすることで、Line Message APIのリクエスト検証が実施される。

## 3.LINE Messaging APIの構築
### 3.1.LINE Messaging API用のLINEアカウント作成とWebhook設定

[LINE Developersのウェブページ](https://developers.line.biz/ja/)にアクセスし、`ログイン`をクリックする。
![20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/30524821-66c9-2fac-5777-b6267b58b348.png)

`LINEアカウントでログイン`をクリックする。
![21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/88380139-3ed9-8741-8774-d4d77ce399db.png)

好きな方法でLINEにログインする。
![22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/ddbb25f3-0558-7d98-8450-b26d7947937c.png)


画面の右下にある言語設定を"English"から"日本語"に変更する。
"開発者名(Developer name)"と"メールアドレス(Your email)"を入力し、契約を確認し同意のチェックをクリックし、`アカウントを作成`をクリックする。
![23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/7cbd28cd-b29b-cd3c-02dd-5d930f383312.png)

ポータル画面に遷移するので画面下部の`新規プロバイダー作成`をクリックする。
![25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/13a6a7a0-f6dc-8262-0dec-51360fe3186a.png)
![26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/4c70ac51-5c33-b529-472b-ce00c152854a.png)

プロバイダー名を入力し、`作成`をクリックする。
![27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/eb461cff-33bd-33eb-2a02-08af4a268ebd.png)

チャネル設定から`Messaging API`をクリックする。
![28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/e39f5e17-1029-2b29-4e72-0d9395a3fb55.png)
![29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/89b962bb-fe25-700e-fb1d-48763b23ff63.png)

"チャンネル名"、"チャンネル説明"、"大業種"、"小業種"の情報を入力し、規約の同意をチェックし、`作成`をクリックする。
![30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/924450ba-0845-36bb-ae6f-1d58e9de4999.png)
![31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/c468ee34-c0e9-c226-4529-f8680f49b0b2.png)
![32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/109a7846-1a3b-f0c2-5b0f-0a991f653ab7.png)
![33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/93870ee5-23a0-1594-4f68-55d05e0ef98d.png)

`OK`をクリックする
![34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/a4f51d21-97a2-bfe1-eab7-25f88fb042a8.png)

`同意する`をクリックする。
![35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/5b3e3846-6d38-21dd-762a-4ec1b1496058.png)

チャンネルが作成されたので、"Messaging API設定"をクリックする。
![36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/1ea67c8f-4909-256f-0f23-1ab6e7a51c1b.png)

下にスクロールするとWebhook URL設定があるので、`編集`をクリックする。Webhook URLに値を入力し`更新`をクリックする。
![37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/c68eabf4-8e68-2c2a-d5f8-75687c403a2e.png)
![38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/b6b6ff97-8fa2-9351-1be7-626e0eca2e22.png)

URLには先程Cloud Functionで設定したURLを設定する。

検証をクリックする。問題なければ、以下のように「成功」のメッセージが表示される。
![39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/727232cd-6b42-e7fb-b5e1-c93b8e34ea5e.png)


`Webhookの利用`をチェックする。
![41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/7529d8e4-ef0e-0744-940b-f1bd68a462f7.png)

### 3.2.LINE Messaging API用の自動応答メッセージの無効化

![91.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/c5540551-b371-c3af-7e35-7f152e22da03.png)
上記の画面で「応答メッセージ」の右側にある「編集」をクリックする。

![92.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/6c23d4c0-ac29-c1c4-38ed-dce0a5a021da.png)
詳細設定の「応答メッセージ設定」をクリックする。

![93.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/7f2b7967-e334-de90-2e6e-83fcb35d5678.png)
「ステータス」が「オン」になっているので「オフ」に切り替える。

![94.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/f8c0883c-6758-e89d-e3e4-3ba8f38a5714.png)
こちらを行うことで、Google Cloud Floumctionで生成したリプライメッセージが返却できる。


## 4.Raspberry Piの設定
### 4.1.TeraTarmでsshログインする

WindowsにてTeraTarmを起動して、ラズパイにSSH接続する。

### 4.2.Firebase環境の構築

・Firebase用のディレクトリ作成

	cd /home/pi;pwd
	mkdir firestoreapp
	ls -ld firestoreapp

・Pythonの仮想環境作成

	cd /home/pi/firestoreapp;pwd
	python -m venv --system-site-packages ./venv
	source ./venv/bin/activate
	python -V
	pip install --upgrade pip

・Firebaseのライブラリーを追加する

	pip install --upgrade firebase-admin

上記のコマンドで、google-cloud-coreとgoogle-cloud-firestoreも入るはず。
ここでインストールされるgrpcio-1.41.1に後々苦しめられることになった。。対処法は次の内容。

・grpcioの再インストール

Pythonアプリを動かす中で以下のようなエラーが発生した。

★エラー１

	from grpc._cython import cygrpc as _cygrpc
	ImportError: /home/pi/firestoreapp/venv/lib/python3.9/site-packages/grpc/_cython/cygrpc.cpython-39-arm-linux-gnueabihf.so: undefined symbol: __atomic_exchange_8

	During handling of the above exception, another exception occurred:

	Traceback (most recent call last):
	  File "/home/pi/firestoreapp/./rs-listener-test.py", line 3, in <module>
	    from firebase_admin import firestore
	  File "/home/pi/firestoreapp/venv/lib/python3.9/site-packages/firebase_admin/firestore.py", line 28, in <module>
	    raise ImportError('Failed to import the Cloud Firestore library for Python. Make sure '
	ImportError: Failed to import the Cloud Firestore library for Python. Make sure to install the "google-cloud-firestore" module.

★エラー２

	  File "/home/pi/firestoreapp/venv/lib/python3.9/site-packages/grpc/__init__.py", line 22, in <module>
	    from grpc import _compression
	  File "/home/pi/firestoreapp/venv/lib/python3.9/site-packages/grpc/_compression.py", line 15, in <module>
	    from grpc._cython import cygrpc
	ImportError: /lib/arm-linux-gnueabihf/libc.so.6: version `GLIBC_2.33' not found (required by /home/pi/firestoreapp/venv/lib/python3.9/site-packages/grpc/_cython/cygrpc.cpython-39-arm-linux-gnueabihf.so)

これらのエラーについていろいろ調査しトライ＆エラーしたところ、これから記載する対応でクリアできた。
どうやらfirebase-adminのインストール途中にgrpcioのインストールがこけていたり、
grpcioが要求するglibcバージョンがインストールされているglibcのバージョンと異なっているためにエラーとなっているらしいと情報を掴んだため、
少し前の世代のgrpcioのインストールパッケージをPyPiから取得し、強制的に再インストールすることにした。

[PyPIのgrpcioのページ](https://pypi.org/project/grpcio/1.40.0/#files)に行き、arm用の「grpcio-1.40.0-cp39-cp39-linux_armv7l.whl」をPCにダウンロードする。

ダウンロードしたパッケージをTera Tarmのscp機能を用いて、ラズパイに配置し、以下のコマンドで再インストールを行う。

	pip install --force-reinstall grpcio-1.40.0-cp39-cp39-linux_armv7l.whl
	
※このコマンドは、ダウンロードしたパッケージを置いた場所で実施すること。

TeraTarmのファイル転送機能を利用し、1.3で取得した認証キーをラズパイのFirebaseディレクトリに格納する。

	ls -l /home/pi/firestoreapp/serviceAccountKey.json


### 4.3.Cloud Storeへのテスト接続

接続確認用のアプリ(rs-listener-test.py)を配置し、実行権限を付与する。

	ls -l /home/pi/firestoreapp/rs-listener-test.py
	chmod 755 /home/pi/firestoreapp/rs-listener-test.py
	ls -l /home/pi/firestoreapp/rs-listener-test.py

接続確認用のアプリのコードは以下のようなもの。

```python:rs-listener-test.py
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore


def main():
    print('auth start')
    cred = credentials.Certificate("./serviceAccountKey.json")
    firebase_admin.initialize_app(cred)
    print('auth finish')

    # Firestore アクセス
    db = firestore.client()
    doc_ref = db.collection(u'line-message') #Firesotre構築時に記載したコレクションIDを指定する。

    # テストデータの登録
    doc_ref.add({
        u'text': u'Test Access Succes'
    })

    # テストデータの読み込み
    docs = doc_ref.stream()
    for doc in docs:
        print(f'{doc.id} => {doc.to_dict()}')


if __name__ == '__main__':
    main()
```

仮想環境の有効化

	cd /home/pi/firestoreapp;pwd
	source ./venv/bin/activate

接続確認用アプリの実行

	python ./rs-listener-test.py

接続が上手くいけば以下のようなデータがログとして出力されます。

	(venv) pi@raspberrypi:~/firestoreapp $ python ./rs-listener-test.py
	auth start
	auth finish
	6WNqnDJCuee2NK4Zrb7y => {'text': 'Test Access Succes'}
	VOd4hHeB8NORqkHW5eNU => {}
	(venv) pi@raspberrypi:~/firestoreapp $

Firestoreのコンソール上でも以下のようにテストデータが値が追加されたことが分かります。

(接続用アプリ実行前)
![test_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/74f1f022-41b6-95a2-d247-36c49f26df7e.png)


(接続用アプリ実行後)
![test_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017667/1d957b4f-0efd-5e2a-95f7-c031802a533a.png)
Test Access Succesのデータが登録されています。

### 4.3.Pythonアプリの配置

本番用のアプリ(rs-listener.py)をTeratarmのSCP機能を用いて配置し、実行権限を付与する。

	ls -l /home/pi/firestoreapp/rs-listener.py
	chmod 755 /home/pi/firestoreapp/rs-listener.py
	ls -l /home/pi/firestoreapp/rs-listener.py

アプリのコードは以下です。

```python:rs-listener.py
import subprocess
import threading
import time
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore


def auth():
    print('auth start')
    # Firebase 初期化
    if not firebase_admin._apps:
        cred = credentials.Certificate("/home/pi/firestoreapp/serviceAccountKey.json")
        firebase_admin.initialize_app(cred)
    print('auth finish')


def listen_document():
    # [START firestore_listen_document]
    print('client set')
    db = firestore.client()

    # Create an Event for notifying main thread.
    callback_done = threading.Event()
    print('set callback')

    # Create a callback on_snapshot function to capture changes
    def on_snapshot(doc_snapshot, changes, read_time):
        for doc in doc_snapshot:
            print(f'Received document snapshot: {doc.id} ,data : {doc.to_dict()} ,message : {doc.to_dict().get("text")}')

            cmd = ["/home/pi/tool/alexa-remote-control/alexa_remote_control.sh", "-e", f"""speak:<amazon:domain name='conversational'>{doc.to_dict().get("text")}</amazon:domain>"""]
            subprocess.call(cmd)

            db.collection(u'line-message').document(doc.id).delete()

        callback_done.set()

    doc_ref = db.collection(u'line-message')

    # Watch the document
    doc_watch = doc_ref.on_snapshot(on_snapshot)
    print('set Watch the document')
    # [END firestore_listen_document]


if __name__ == '__main__':
    auth()
    listen_document()
    while True:
        time.sleep(5)

```

## 5.動作確認

### 5.1.Alexaの起動

以下のコマンドで起動。

	cd /home/pi/sdk-folder/sdk-build
	PA_ALSA_PLUGHW=1 ./SampleApp/src/SampleApp ./Integration/AlexaClientSDKConfig.json ../third-party/snowboy/resources INFO
	
※Alexaのインストールについては、[Raspberry piのスマートスピーカー化 (Amazon Alexaの公式インストールシェルが利用できないのでソースからインストールしてみた)](https://qiita.com/kanamekun/items/12f5d970509b575edce4)の記事を参照ください。


### 5.2.Pythonアプリの起動

仮想環境の有効化

	cd /home/pi/firestoreapp;pwd
	source ./venv/bin/activate

Pythonアプリの実行

	python ./rs-listener.py

以下のようなログが出力され、Cloud Firestoreの更新待ちとなる。

	(venv) pi@raspberrypi:~/firestoreapp $ python ./rs-listener.py
	auth start
	auth finish
	client set
	set callback
	set Watch the document


### 5.3.LINEでテスト

ここまでで動作確認の準備は終了。
以下の動画のように、LINEを使ってメッセージを入力してみました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ラズベリーパイにインストールしたAmazon AlexaをLINEで自由に喋らせたく連携システムを構築しました。構成は、LINEアプリ×LINE Messaging Api×Google Cloud Function×Cloud Firestore×ラズパイ内のpythonアプリとalexa。動画は、LINEで入力したメッセージをalexaが喋る様子です。 <a href="https://t.co/eOpaeB4D6p">pic.twitter.com/eOpaeB4D6p</a></p>&mdash; kaname kun (@kanamekunkun) <a href="https://twitter.com/kanamekunkun/status/1463775467085856772?ref_src=twsrc%5Etfw">November 25, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

LINEに入力した「こんにちは、今日はいい天気ですね」が、無事 Alexaから再生され、やりたいことが実現できました。
いろいろと手こずりましたが、何とか達成できて嬉しいです。

また、LINEで入力したメッセージを無視されることがあることが分かりました。
応答メッセージがないので、LINE Messaging APIとCloud Function間の連携でこけていると予想します。
やりたいことが出来たのでいったん完了とし、調査はまた今度とします。


# 追記 
## 追記1(2021/11/26).LINEで入力したメッセージが無視されることがある件の改善

> また、LINEで入力したメッセージを無視されることがあることが分かりました。
> 応答メッセージがないので、LINE Messaging APIとCloud Function間の連携でこけていると予想します。
> やりたいことが出来たのでいったん完了とし、調査はまた今度とします。

こちら調査したところ、Cloud Functionにおけるfirebase_adminの初期化処理において、初期化済みであるにも関わらずも一度初期化を行ったときに発生するエラーで処理がこけていました。

[check if a Firebase App is already initialized in python](https://stackoverflow.com/questions/44293241/check-if-a-firebase-app-is-already-initialized-in-python)

★エラー

	in initialize_app raise ValueError(( ValueError: The default Firebase app already exists. This means you called init	ialize_app() more than once without providing an app name as the second argument. In most cases you only need to cal	l initialize_app() once. But if you do want to initialize multiple apps,
	pass a second argument to initialize_app() to give each app a unique name.


関数の一回目と二回目の起動時間が短いと、一回目の関数で初期化したfirebase_adminが破棄されずに残っているようです。
プログラムの中で、firebase_admin._appsが残っているかどうか判定してから初期化するコードに改善しました。

```python:main.py
# Firebase 初期化
if not firebase_admin._apps:
    cred = credentials.Certificate("./serviceAccountKey.json")
    firebase_admin.initialize_app(cred)
```

## 追記2(2021/11/26). Pythonのアプリの自動起動設定

Alexaを自動起動しているのに、わざわざPythonアプリを手動起動させるのは面倒なので、自動起動設定をしました。

以下のファイルの読み込みをフルパス指定に変更。

```python:rs-listener.py
cred = cedentials.Certificate("/home/pi/firestoreapp/serviceAccountKey.json")
```

起動スクリプトの作成。

	vi /home/pi/alexa-line-control.sh
	chmod 755 /home/pi/alexa-line-control.sh

alexa-line-control.shシェルの中身は以下の通り。

	#!/bin/bash
	
	source /home/pi/firestoreapp/venv/bin/activate
	python /home/pi/firestoreapp/rs-listener.py



自動起動用のファイルを作成する。

	sudo vi /etc/systemd/system/lineControler.service


ファイルの中身は以下の通り。

	[Unit]
	Description = Line Controler for Alexa
	[Service]
	Restart = always
	WorkingDirectory=/home/pi/
	ExecStart = /home/pi/alexa-line-control.sh
	ExecReload = /bin/kill -s HUP ${MAINPID}
	ExecStop=/bin/kill -s TERM ${MAINPID}
	user=pi
	[Install]
	WantedBy = multi-user.target



システムコマンドで起動できるか確認。

	sudo systemctl start lineControler.service
	sudo systemctl status lineControler.service



起動すると以下のようなメッセージが出る。

	pi@raspberrypi:~ $ sudo systemctl status lineControler.service
	● lineControler.service - Line Controler for Alexa
	   Loaded: loaded (/etc/systemd/system/lineControler.service; enabled; vendor preset: enabled)
	   Active: active (running) since Fri 2021-11-26 13:36:42 JST; 4s ago
	 Main PID: 1241 (alexa-line-cont)
	    Tasks: 2 (limit: 1935)
	   CGroup: /system.slice/lineControler.service
	           tq1241 /bin/bash /home/pi/alexa-line-control.sh
	           mq1242 python /home/pi/firestoreapp/rs-listener.py

	11月 26 13:36:42 raspberrypi systemd[1]: Started Line Controler for Alexa.
	pi@raspberrypi:~ $



システムコマンドで停止できるか確認。

	sudo systemctl stop lineControler.service
	sudo systemctl status lineControler.service


停止が上手くいけば以下のようなメッセージが出る。

	pi@raspberrypi:~ $ sudo systemctl status lineControler.service
	● lineControler.service - Line Controler for Alexa
	   Loaded: loaded (/etc/systemd/system/lineControler.service; enabled; vendor preset: enabled)
	   Active: inactive (dead) since Fri 2021-11-26 13:38:00 JST; 4s ago
	  Process: 1241 ExecStart=/home/pi/alexa-line-control.sh (code=killed, signal=TERM)
	  Process: 1292 ExecStop=/bin/kill -s TERM ${MAINPID} (code=exited, status=0/SUCCESS)
	 Main PID: 1241 (code=killed, signal=TERM)

	11月 26 13:36:42 raspberrypi systemd[1]: Started Line Controler for Alexa.
	11月 26 13:38:00 raspberrypi systemd[1]: Stopping Line Controler for Alexa...
	11月 26 13:38:00 raspberrypi systemd[1]: lineControler.service: Main process exited, code=killed, status=15/TERM
	11月 26 13:38:00 raspberrypi systemd[1]: lineControler.service: Succeeded.
	11月 26 13:38:00 raspberrypi systemd[1]: Stopped Line Controler for Alexa.
	pi@raspberrypi:~ $



最後に自動起動設定の有効化

	sudo systemctl enable lineControler.service


これでラズパイ起動時に「rs-listener.py」が自動実行されるはず。


## 追記3(2022/08/24). CloudFunctionにおけるランタイム環境変数の設定

同じような事を再びやる機会があったため、本文を見直したところランタイム環境変数の設定手順が無いことに気づいた。
修正を実施しました。

>Google Cloud Founctionにランタイム環境変数に以下を設定する。
>LINE_ACOUNT_SECRET：LINE Messageing APIのチャネルシークレット
>LINE_ACCOUNT_ACCESS_TOKEN：LINE Messageing APIのチャネルアクセストークン
>
>そうすることで、Line Message APIのリクエスト検証が実施される。

# 参考文献
本記事の作成に当たり、以下の情報を参考にさせて頂きました。

- [Messaging APIを始めよう](https://developers.line.biz/ja/docs/messaging-api/getting-started/)
- [メッセージ（Webhook）を受信する](https://developers.line.biz/ja/docs/messaging-api/receiving-messages/#verifying-signatures)
- [LINEのメッセージでRaspberryPIのLEDをON／OFFしてみる（その１）設定](https://note.com/khe00716/n/n34bb4c087fdc)
- [Google HomeにLINEのメッセージを読み上げさせる(LINE BOT+Webhook+Firebase+Node.js)](https://megalodon.hateblo.jp/entry/2018/01/10/014604)
- [LINE MessagingAPIで遊ぶ①動かしてみる)](https://qiita.com/ogrium/items/551051cf1285ae50f76b)
- [Cloud Firestore でリアルタイム アップデートを入手する](https://firebase.google.com/docs/firestore/query-data/listen?hl=ja#python)
- [PythonのOnSnapshotを持つFirebase Cloud FireStore.](https://www.fixes.pub/program/857636.html)
- [リアルタイムなデータの保存・同期を手軽に実現するCloud Firestore入門](https://engineer.blog.lancers.jp/event/introduction_cloud_firestore/)
- [【解決】 Python3のfirebase_adminでCollectionをon_snapshot()してるとhread-ConsumeBidirectionalStream caught unexpected exception](https://uyamazak.hatenablog.com/entry/2019/07/09/221041)
- [Cloud Firestore を使ってみる](https://firebase.google.cn/docs/firestore/quickstart?hl=ja)
- [[Python] ImportError: Failed to import the Cloud Firestore library for Python.](https://www.rm48.net/post/python-importerror-failed-to-import-the-cloud-firestore-library-for-python)
- [LineBotをCloud Functionsで利用してGCEの起動停止できるようにしてみた](https://yysuke.hateblo.jp/entry/2019/08/29/145643)
- [check if a Firebase App is already initialized in python](https://stackoverflow.com/questions/44293241/check-if-a-firebase-app-is-already-initialized-in-python)
- [Raspberry Piで最強の防犯カメラを作ってみる（動画記録・配信、動体検知・Line通知、顔検知・顔認証、Alexa搭載）[5/6]
](https://dream-soft.mydns.jp/blog/developper/smarthome/2020/02/707/#toc8)