スマートスピーカーを使って日々の健康状態を記録してみた 

## はじめに
ウェアラブル端末はヘルスケア目的としても活用が可能で、利用者は睡眠や運動などのデータを連携アプリから確認して日々の健康管理ができます。

これらのデータに加えて、スマートスピーカーからも主観的な健康状態を記録し観察することで、もっと確実な健康管理ができるのではないかと考え、この仕組みを作ってみることにしました。

## 完成イメージ
決まった時刻になると、Amazon Echoが3つの質問を対話形式で行います。全ての質問に答えると、バックエンドのデータベースに回答した健康情報を登録します。
![完成イメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506008/d5d50014-4281-5d85-0f57-9d7fe04c66a1.png)

## 概要
今回紹介するAlexaスキルを作るには以下の3つの実装と設定が必要です。
本記事では、初めてAlexaスキルを作成することを想定し、事前準備として必要なアカウント登録方法から順に記載していきます。

- Alexaスキルの設定
  - 呼び出し名の設定
  - インテントの設定
- バックエンドの実装
  - 発話された内容に応じた処理（DBの登録、対話形式）
- Alexaアプリの設定
  - 定刻にスキルを起動

## 目次

[1. Alexaスキルのアカウント登録](#1-alexaスキルのアカウント登録)
[2. AlexaスキルとLambdaの連携](#2-alexaスキルとlambdaの連携)
[3. Alexaスキルの設定](#3-alexaスキルの設定)
[4. バックエンドの実装](#4-バックエンドの実装)
[5. Alexaアプリの設定](#5-alexaアプリの設定)
※1と2は、参考になる記事のリンクを貼らさせていただいて詳細を割愛しています。

## 1. Alexaスキルのアカウント登録
Amazon DeveloperとAWSの2つのアカウントが必要です。
* Amazon Developer アカウント
[Amazon.co.jp](https://www.amazon.co.jp/)アカウントを作成してから、そのアカウントで[Amazon Developer](https://developer.amazon.com/ja/)にログインします。
この順番で作成しないとAlexaアプリとの連携などに失敗することがあるようです。（参照：[失敗しないAlexa開発者アカウントの作り方](https://dev.classmethod.jp/articles/how-to-safety-create-alexa-developers-account/)）
* AWS アカウント
[AWS](https://aws.amazon.com/jp/)のコンソールにサインインからAWSアカウントを作成する（Lambda関数の実装に必要）

## 2. AlexaスキルとLambdaの連携
Alexaスキル作成時に、バックエンドリソースをホスティングする方法を聞かれます。（「ユーザー定義のプロビジョニング」を選択）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506008/fd92e518-d47f-b9ec-d564-6032a2dba17a.png)
バックエンドでは発話に応じた処理（今回で言うと対話形式の実現と会話情報の登録）を行います。
Alexa自身にもホスティングが可能ですが、他のAWSサービスと連携が容易に出来るように独自バックエンドとしてLambda関数を使用します。
![バックエンドサービス.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506008/bfa7201f-6ec1-1cc2-2763-93724d9e3539.png)
この他にもLambda連携の設定が必要です。以下の記事が参考になります。
（参照：[AlexaスキルをPython/Lambdaで実装する](https://qiita.com/nsuhara/items/5b19cfb5ffb897bd4cfd)）

## 3. Alexaスキルの設定
#### 呼び出し名
スキルを呼び出すときの名前を指定します。
「アレクサ、〇〇を開いて」と話しかけることでスキルを起動することができます。
今回はスケジューリング設定により起動するので直接呼び出すことはないですが、後でこの名前を使用するため「健康管理アプリ」と入力してモデルを保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506008/1a78b580-849f-9276-3107-9faac1af6d7b.png)
#### インテント
インテントではAlexaがどんな言葉を受け付けるかを予め設定するもので、その言葉に応じてバックエンドの処理を実装します。
「今日の気分はどうですか？」というオープンな質問に対して、回答のバリエーションは様々で全通り定義できないため、全ての言葉を受け付けるインテントを設定します。
JSONエディターを開いて以下のJSONを入力し、モデルを保存、モデルをビルドを実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506008/16baa312-2a9f-7c3b-8404-203b31704d70.png)

```json
{
    "interactionModel": {
        "languageModel": {
            "invocationName": "健康管理アプリ",
            "intents": [
                {
                    "name": "Condition",
                    "slots": [
                        {
                            "name": "utterance",
                            "type": "any"
                        }
                    ],
                    "samples": [
                        "{utterance}"
                    ]
                }
            ],
            "types": [
                {
                    "name": "any",
                    "values": [
                        {
                            "name": {
                                "value": "ほげほげ"
                            }
                        }
                    ]
                }
            ]
        }
    }
}
```
"types"はカスタムスロットタイプと呼ばれるもので、anyという名前で作成しています。これをConditionインテントのタイプに設定することで、全ての言葉をConditionインテントで受け付けることができます。
（参照：[ユーザーの自由発話に対応する Alexa Skill を作る](https://qiita.com/halhorn/items/48998d6b7a166cc905c6)）

## 4. バックエンドの実装
Alexaスキルが起動したときの初期処理と、Conditionインテント（全ての言葉）を受け取ったときに行う処理をバックエンドで実装します。処理内容は、質問に回答したら次の質問をリクエストして、全ての質問に回答したらデータ登録APIを叩いて外部データベースに登録します。

「2. AlexaスキルとLambda関数の連携」で作成したLambda関数に以下のコードをデプロイします。(ランタイムはPython3.9)

```python
import os
import requests 
import urllib3

from urllib3.exceptions import InsecureRequestWarning
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_core.skill_builder import SkillBuilder
from ask_sdk_core.utils import get_slot_value, is_intent_name, is_request_type
from ask_sdk_model import Response
from ask_sdk_model.ui import SimpleCard

sb = SkillBuilder()

# POSTリクエスト時に発生するWARNINGを無視する
urllib3.disable_warnings(InsecureRequestWarning)
# Alexaから受けた質問が何回目かをカウントする（Conditionインテントが呼ばれた回数）
question_counter = 0
# Alexa経由で発話された健康データを保管
health_data={}

# Alexaスキルを起動したときに呼び出される関数
@sb.request_handler(can_handle_func=is_request_type("LaunchRequest"))
def launch_request_handler(handler_input):
    # ローバル変数を読み込む
    global question_counter
    # 質問カウンタを1に初期化
    question_counter = 1
            
    # 1回目の質問
    speech_text = "今日の気分はどうですか？"

    handler_input.response_builder.speak(speech_text).set_card(
        SimpleCard("健康管理アプリ", speech_text)).set_should_end_session(
        False)
    return handler_input.response_builder.response

# Alexaの質問に回答したときに呼び出される関数（自由テキストのため何を発話してもこのインテントが起動）
@sb.request_handler(can_handle_func=is_intent_name("Condition"))
def health_telling_intent_handler(handler_input):
    # グローバル変数を読み込む
    global question_counter
    global health_data
    
    # 回答した発話を読み込む
    user_condition = get_slot_value(handler_input=handler_input, slot_name="utterance")
    print(user_condition)
    
    # 1回目の質問に回答したとき
    if question_counter == 1:
        # 気分の状態を変数に保管
        health_data['condition'] = user_condition
        # 次の質問をAlexaに発話させる
        speech_text = "ストレス値はどうですか？"
        handler_input.response_builder.speak(speech_text).set_card(
        SimpleCard("健康管理アプリ", speech_text)).set_should_end_session(False)
        # 質問カウンタをインクリメント
        question_counter += 1

    # 2回目の質問に回答したとき
    elif question_counter == 2:
        # ストレスの状態を変数に保管
        health_data['stress'] = user_condition
        # 次の質問をAlexaに発話させる
        speech_text = "睡眠はどうですか？"
        handler_input.response_builder.speak(speech_text).set_card(
        SimpleCard("健康管理アプリ", speech_text)).set_should_end_session(False)
        # 質問カウンタをインクリメント
        question_counter += 1
    
    # 3回目の質問に回答したとき    
    elif question_counter == 3:
        # 睡眠の状態を変数に保管
        health_data['sleep'] = user_condition
        # 登録用にデータを加工/ヘッダーを作成
        json_data = {'health':health_data}
        headers = { 'accept': 'application/json' }
        print(json_data)
        # POSTメソッドで健康データを送信
        response = requests.post(os.getenv('POST_URL'), headers=headers, json=json_data, verify=False)
        print(response)
        # 会話を終了する
        speech_text = "健康状態を登録しました。"
        handler_input.response_builder.speak(speech_text).set_card(
        SimpleCard("健康管理アプリ", speech_text)).set_should_end_session(True)
        # 質問カウンタをインクリメント
        question_counter += 1
        
    else:
        speech_text = "登録に失敗しました。最初からやり直してください。"
        print(question_counter)
        handler_input.response_builder.speak(speech_text).set_card(
        SimpleCard("健康管理アプリ", speech_text)).set_should_end_session(True)
        # 質問カウンタを0に初期化
        question_counter = 0
        
    return handler_input.response_builder.response

@sb.request_handler(can_handle_func=is_request_type("SessionEndedRequest"))
def session_ended_request_handler(handler_input):
    # type: (HandlerInput) -> Response

    return handler_input.response_builder.response


@sb.exception_handler(can_handle_func=lambda i, e: True)
def all_exception_handler(handler_input, exception):
    # type: (HandlerInput, Exception) -> Response
    print(exception)

    speech = "すみません、わかりませんでした。もう一度言ってください。"
    handler_input.response_builder.speak(speech).ask(speech)
    return handler_input.response_builder.response


lambda_handler = sb.lambda_handler()
```

データベースへの登録は外部サービスのデータ登録APIを別途作成して、API経由で転送する実装になっています。別クラウドに連携するためにデータを転送していますが、DynamoDBのboto3を使用すれば外部用のAPIを作成しなくてもよいです。

## 5. Alexaアプリの設定
Alexaスキルの設定とLambda関数の連携と実装が正しくできれば、Alexaスキルと同じアカウントでログインしているAmazon Echoに次のように呼びかけるとスキルを起動できます。
> 私「アレクサ、健康管理アプリを開いて」
> Echo「今日の気分はどうですか？」
> ⋮  

定刻に起動するためには、スマホ版のAlexaアプリをインストールして、スケジューリングを設定します。設定方法は以下の通りです。
![Alexaアプリ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506008/8a0477f2-d76f-b0d6-2d5f-a3d1612fa7b6.png)
その他-定型アクションから、定型アクションを作成します。
* 名前：任意の名前
* 実行条件：スケジュールを選択して、時刻とパターンを設定
* アクション：カスタムを選択して、フレーズに「健康管理アプリを開いて」を入力
* デバイス：連携しているAmazon Echoを選択

## おわりに
現在は仕組みを作っただけで、データを活用するところまでは至っていません。例えば、Fitbitで収集したデータと主観的な健康状態を組み合わせることで、スマートスピーカーが定期的に健康をアドバイスしてくれたら価値があるのではと考えています。