Alexaでシャッフル睡眠を促してくれるアプリを作ってみたい

長らく愛用してきたシャッフル睡眠を促してくれるAlexaアプリが公開停止されて私の安眠が失われてしまったので自作しようと思います。

# 作成予定のスキル
認知シャッフル睡眠法をサポートしてくれるアプリを作成予定です。
具体的には数秒おきにランダムな単語を読み上げるようにするシンプルなアプリです。

> 認知シャッフル睡眠法とは
>
>・カナダの認知科学者リュック・ボードウィン博士が考案した睡眠法
>・「大脳皮質が理論的な活動をしているうちは、脳が『まだ寝てはいけない』と
判断する」という仕組みを利用して、あえて何の脈絡もないイメージを連想し、大脳皮質の理論的な活動を止め、脳が“眠りのスイッチを入れても大丈夫”と判断するように導いてゆくもの
>・適当な単語を次々とイメージすることで睡眠を妨げる思考が抑制されることで眠りにつける

# 事前準備
## 1. アカウント作成
alexa developer consoleにアクセスしアカウントを作成してログインします。

## 2. アプリ作成
スキルの作成から今回開発するアプリを作成します。
```text: 設定内容
アプリ名: シャッフル睡眠
プライマリロケール: 日本語
エクスペリエンスのタイプ: その他
モデル: カスタム
ホストするサービス: Alexa-hosted（Python）
ホストするリージョン: 米国西部 (オレゴン)
テンプレート: スクラッチ
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2596660/3a794f36-1f33-8526-5517-65013c3bb188.png)

# 開発
## まずはシンプルなものを作る
最初はテストがしやすい最低限の機能を実装しようと思います。
- スキルが開始されたら登録済みの単語を5秒間隔で読み上げる

”コードエディタ”タブで`lambda_function.py`を開き`LaunchRequestHandler`を以下に置き換えます。

:::note info
Alexaの音声を制御する方法として[SSML(音声合成マークアップ言語)](https://developer.amazon.com/ja-JP/docs/alexa/custom-skills/speech-synthesis-markup-language-ssml-reference.html)というものがあります。
今回はその中の`break`(音声の中での休止)を使用しています。
:::
```python
class LaunchRequestHandler(AbstractRequestHandler):
    """Handler for Skill Launch."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool

        return ask_utils.is_request_type("LaunchRequest")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        words = ['犬', '猫']
        
        speak_output = 'シャッフル睡眠を開始します。'
        
        for word in words:
            speak_output+= "<break time ='5s' />" + word

        return (
            handler_input.response_builder
                .speak(speak_output)
                # .ask(speak_output) ユーザに聞き返さないのでコメントアウト
                .response
        )
```

これでテストしてみます。
”テスト”タブを開き、Alexaシミュレータをしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2596660/11e9a946-f326-cae9-a611-3ca9633e6411.png)
ちゃんと動いていそうですね。
テストすると実際に音声も返ってきます。ちゃんと五秒毎に「犬...猫」と読み上げてくれました。

## 登録した単語をランダムに並び替える
現在は登録してある単語をそのままの順で読み上げてるだけなのでランダムに並び替えましょう。
```python: 追加import
import random
```
```python
class LaunchRequestHandler(AbstractRequestHandler):
    """Handler for Skill Launch."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool

        return ask_utils.is_request_type("LaunchRequest")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        words = ['犬', '猫', '鳥', '魚']
        random.shuffle(words)
        
        speak_output = 'シャッフル睡眠を開始します。'
        
        for word in words:
            speak_output+= "<break time ='5s' />" + word

        return (
            handler_input.response_builder
                .speak(speak_output)
                # .ask(speak_output) ユーザに聞き返さないのでコメントアウト
                .response
        )
```
テストしてみるとちゃんとランダムになっていますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2596660/e32e2d46-72d3-2694-6ac9-0f48fb3260db.png)

## 最大時間を設定する
永遠と単語を垂れ流すのもアレなので最大時間を設定しましょう。
1単語の発音を0.5sくらいだと仮定して
```text:1単語にかかる時間
1単語の発音[0.5s] + インターバル[5s] = 5.5s
```
最大時間を一旦テストのために1分として
```text: 最大単語数
最大時間[60s] / 1単語にかかる時間[5.5s] = 10.909.. ≒11
```
となるので11単語ほど読み上げれば良さそうですね。

以下に変更してテストします。
```python: 追加import
import random
import math
```
```python
class LaunchRequestHandler(AbstractRequestHandler):
    """Handler for Skill Launch."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool

        return ask_utils.is_request_type("LaunchRequest")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        words = ['犬', '猫', '鳥', '魚', '虎', '豚', '羊', '牛', '馬', '猿', '狼', '狐', '熊', '鹿', '蛇', '蟹', '象', '狸', '鼠', '蝶']
        random.shuffle(words)
        
        max_time = 60
        max_word_len = math.ceil(max_time / (1 + 5))
        
        speak_output = 'シャッフル睡眠を開始します。'
        
        for index, word in enumerate(words):
            if index > max_word_len - 1:
                break
            speak_output+= "<break time ='5s' />" + word

        return (
            handler_input.response_builder
                .speak(speak_output)
                # .ask(speak_output) ユーザに聞き返さないのでコメントアウト
                .response
        )
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2596660/5f58214a-9a2e-36aa-1890-9b0e519e1ed6.png)
最大で11個になってるので大丈夫そうですね。

## 微修正して完成！！
ある程度、形になってきたので微修正して体裁を整えます。
- 登録している単語を適切な単語群に置き換え
    - chatGPTなど使って生成してもらいました
- 最大時間を10分に変更

# 最後に
今回はシャッフル睡眠を促すアプリを作ってみました。

以下が今後の課題なのでまた機会(とやる気)があれば書くかもです。
- インターバルの秒数や最大時間をユーザ側で設定できる機能の追加
- 単語群の追加
    - 単語群が大きくなってくると並び替えにも時間がかかってくる可能性があるので考慮が必要？
- 実際に公開してみる