Androidの通知をゆっくりに喋ってもらう

[Macrodroid](https://play.google.com/store/apps/details?id=com.arlosoft.macrodroid&hl=ja)というAndroidアプリとRaspberry piを使って、スマホの通知をゆっくりボイスが喋ってくれるシステムを作ってみました。

# 概要
MacrodroidはAndroidでタスク自動化ができるアプリです。
Androidでの何らかのイベントをトリガーに様々なアクションを自動で実行できます。

これとラズパイに立てた簡易webサーバーとを連携させ、通知が来たらその内容をwebサーバーに送り[AquesTalkPi](https://www.a-quest.com/products/aquestalkpi.html)で音声を出力します。

# 環境
- Android10
- Raspberry pi 3b (Raspbian stretch)
  - dockerインストール済み
- 3.5mmイヤホンジャックスピーカー

# ラズパイ側の設定
ラズパイを使っていると、SDカードを交換して環境を作り直すことがあります。そんなときDockerfileがあると、いちいち環境構築の方法を思い出さずに済みます。

ということで簡易webサーバーをDockerで動かします。
(今回はコード量も殆ど無いのでDockerは大げさな気もしますが)

今回作成したDockerfileは次の通りです。

```dockerfile:Dockerfile
FROM balenalib/rpi-raspbian:buster

RUN apt-get update && \
    apt-get -y install --no-install-recommends  alsa-utils python3 && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# https://www.a-quest.com/products/aquestalkpi.htmlのプログラム使用許諾を読んだ上で下記のダウンロードを行う
RUN mkdir /root/bin && \
    curl -kL "https://www.a-quest.com/cgi-bin/download.php?download=1&readed=yes" -o /tmp/aquestalk.tgz && \
    tar xzvf /tmp/aquestalk.tgz -C /tmp && \
    mv /tmp/aquestalkpi/AquesTalkPi /root/bin && \
    mv /tmp/aquestalkpi/aq_dic /root/bin && \
    rm /tmp/aquestalk.tgz && rm -r /tmp/aquestalkpi
ADD ./softalk_server.py /root/bin

EXPOSE 8000
CMD ["python3", "/root/bin/softalk_server.py"]
```

また簡易webサーバーはPythonで動かしました。

```python:softalk_server.py
# -*- coding:utf-8 -*-
from http.server import HTTPServer, SimpleHTTPRequestHandler
from urllib import parse
import subprocess
KEYS = ["app", "title", "message"]


# textをゆっくりボイスにしゃべってもらう
def talk(text):
    p1 = subprocess.Popen(
        ["/root/bin/AquesTalkPi", text],
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
    )
    p2 = subprocess.Popen(
        ["aplay"],
        stdin=p1.stdout, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
    )
    p1.stdout.close()  # SIGPIPE if p2 exits.
    p2.communicate()[0]


# [app, title, messa]のキーからなる辞書text_dicをもとに
# 発話メッセージを用意する
def format_message(text_dic):
    if text_dic['title'] is None:
        return None
    if text_dic['message'] is None:
        return None

    if len(text_dic['message']) < 40:
        formatted = '{app}から{title}というタイトルのメッセージです。{message}'.format(
            **text_dic
        )
    else:
        formatted = '{app}から{title}というタイトルのメッセージです。'.format(
            **text_dic
        )
    return formatted


# 簡易webサーバー
# GETリクエストを受け取ったら、そのパラメータをもとに
# スピーカーからゆっくりボイスを再生する
class RequestHandler(SimpleHTTPRequestHandler, object):
    def do_GET(self):
        query = parse.urlparse(self.path).query
        query_dic = parse.parse_qs(query)
        self.send_response(200)
        for key in KEYS:
            if key not in query_dic:
                query_dic[key] = None
            else:
                query_dic[key] = query_dic[key][0]
        print('query = {}'.format(query_dic))
        formatted = format_message(query_dic)
        # GETリクエストのパラメータが想定されているものと
        # 違う場合はformattedはNoneになり音声は再生されない
        if formatted is not None:
            talk(formatted)
        self.end_headers()


if __name__ == '__main__':
    text = '通知サーバを開始しました'
    talk(text)

    httpd = HTTPServer(("", 8000), RequestHandler)
    httpd.serve_forever()

```

そして`Dockerfile`と`softalk_server.py`があるディレクトリで以下のコマンドを実行します。

```bash
$ docker build -t softalk_notification .
$ docker run -d --name notification_server --device /dev/snd -p 8000:8000 --restart=always softalk_notification
```

`--restart=always`のオプションをつけることで、再起動したときにも(dockerデーモンが停止したときにも)自動でコンテナを立て直すようになり便利です。

また、ALSAを使ってスピーカーの音を再生するために`dev/snd`を`--device`に渡しています。

加えてwebサーバーのポートはコンテナもホストも`8000`に設定しています。


うまくいっていたら次のGETリクエストでゆっくりが喋ってくれるはずです。

```bash
$ curl localhost:8000 --get --data-urlencode 'app=ゆっくり' --data-urlencode 'title=テスト' --data-urlencode 'message=ゆっくりしていってね'
```

# Android側の設定
続いてAndroidに通知が来たときにwebサーバにGETリクエストを出しましょう。これはMacrodroidで実現できます。

[Google Play](https://play.google.com/store/apps/details?id=com.arlosoft.macrodroid&hl=ja)からMacrodroidをインストールしたら、以下のマクロを作成します。

- トリガー
  - 「機器のイベント」 > 「通知」 をタップ
  - 「通知が表示された時」 > 「どれかのアプリ」 > OK をタップ (場合によっては通知領域に関する権限設定が必要)

- アクション
  - 「アプリケーション」 > 「Webサイトを開く / HTTP GET」をタップ
  - 「エンコード・パラメータURL」にチェック
  - `URLを入力`の部分に以下を入力(ラズパイのIPアドレスが192.168.0.2の場合)して右下の`OK`をタップ
  
  ```plaintext:URL
192.168.0.2:8000?app=[not_app_name]&title=[not_title]&message=[notification]
```

トリガーとアクションの設定が終わったら画面右下のチェックマークをタップ。カテゴリーやマクロ名は適当に入力してください。

以上で設定は終了です。

# おわりに
今回は最低限の設定ですので実用的には細かな微調整が必要かなと思います。以下は現状で思い浮かぶ設定項目です。

- 全てのアプリの通知を読み上げるのか : Macrodroid側である程度指定可能
- 常に読み上げを行うのか: Macrodroidの時刻のトリガー(Nature remoと組み合わせて明るさで判定するのも面白そう)

興味を持たれた方は試してみてはいかがでしょうか。