Python
RaspberryPi
人工知能
Alexa
スマートスピーカー

「アレクサ、今の気分におすすめの音楽をかけて♪」を作ってみた(スマートウォッチ×Alexaスキル)

Amazon Echo(Alexa)、スマートウォッチ、ラズベリーパイを使用して、今の気分におすすめの音楽をかけるシステムを作ってみました。

気分の定量化には、スマートウォッチから測定する心拍数を利用しました。心拍数に応じて、落ち着く音楽、もしくはテンションが上がる音楽を再生します。

alexa_1.png

使い方

使い方を動画と文章で解説します。

こちら動画です。
はじめに通常に実行します。その後時計を外して心拍数0で実行します。

●動画ファイルはこちら

文章で解説します。

  1. スマートウォッチを心拍数転送モードに切り替える
  2. ラズベリーパイでheart_rate_monitor.pyを実行。心拍数を5秒に1度、JSONファイルに書き出す
  3. Alexaから作成したAlexaスキルを呼び出す。スキル名はアイアンマンとマーク・ザッカーバーグに憧れ、”JARVIS”にしました。「アレクサ、ジャービスを起動」
  4. ジャービスが応答するので命令する。「ジャービス、今の気分におすすめの音楽をかけて♪」
  5. ラズベリーパイで自宅サーバーを組み、jarvis.pyを実行しておく。このプログラムがAlexaスキルのエンドポイントになっている。jarvis.pyはjsonファイルを読み取り、基準の心拍数より高いか低いかを判定。対応した音楽のURLをAlexaに返す
  6. Amazon Echoが対応するURLの音楽をS3からダウンロードし、再生

フローの概要図は以下の通りです。

alexa_2.png

作り方

スマートウォッチの用意

心拍数をリアルタイムに転送できるスマートウォッチとして、GARMINのvívomove™ HR Premium BlackSilverを用意しました。背面に光学式心拍計がついています。

alexa_4.png

GARMINはGPSやスポーツウォッチの会社です。ANT+という通信形式でジムのランニングマシンにリアルタイムに心拍数を転送できるスマートウォッチを販売しています。

今回はそのビジネスユースのタイプを用意しました(標準からベルトは替えています)。
画面をタップして、心拍転送モードにすると、リアルタイムに心拍が測定されます。

alexa_3.png

以上で心拍数をANT+で送信できます。

ラズベリーパイで心拍数を受信し、JSONファイルに書き込む

心拍数を受信して、JSONファイルに書き込むために「Raspberry Pi 3 Model B」を用意しました。

alexa_5.png

初めてラズベリーパイを使用したので、Raspberry Pi3 Model B【本体+コンプリートスターターキット】を用意しました。

合わせて、ANT+を受信するために、スント(SUUNTO) Bluetooth データ受信機 ムーブスティック ミニ USBを用意しました。ラズベリーパイのUSBポートに差し込みます。

続いて、ANT+をPythonで扱えるパッケージpython-antをインストールします。

ラズベリーパイでANT+を扱う情報については、以下の記事を参考にさせていただきました。
●Raspberry Piで心拍数を監視して心臓が止まりそうになったらアラート音を鳴らす

Pythonプログラムheart_rate_monitor.pyを作成します。
リアルタイムに心拍数を受信し、5秒に1度心拍数をSONファイル(heart_rate.jason)を書き出すプログラムです。

プログラムはGitHubに置いています。

ラズベリーパイでheart_rate_monitor.pyを実行します。
●heart_rate_monitor.py

これでリアルタイムの心拍データがJSONファイルに書き出せるようになりました。

heart_rate_monitor.py
# -*- coding: utf-8 -*-

"""
    Code based on:

https://github.com/mvillalba/python-ant/blob/develop/demos/ant.core/03-basicchannel.py

    in the python-ant repository and

https://github.com/tomwardill/developerhealth

    by Tom Wardill and

https://www.johannesbader.ch/2014/06/track-your-heartrate-on-raspberry-pi-with-ant/

    by Johannes Bader
"""
import sys
import time
from ant.core import driver, node, event, message, log
from ant.core.constants import CHANNEL_TYPE_TWOWAY_RECEIVE, TIMEOUT_NEVER

import json

class HeartRateMonitor(event.EventCallback):

    DEFAULT_SERIAL = "/dev/ttyUSB0"
    DEFAULT_NETKEY = 'B9A521FBBD72C345'.decode('hex')

    def __init__(self, call_func, serial = None, netkey = None):
        self.call_func = call_func
        self.serial = serial or HeartRateMonitor.DEFAULT_SERIAL
        self.netkey = netkey or HeartRateMonitor.DEFAULT_NETKEY
        self.antnode = None
        self.channel = None

    def start(self):
        #print("starting node")
        self._start_antnode()
        self._setup_channel()
        self.channel.registerCallback(self)
        #print("start listening for hr events")

    def stop(self):
        if self.channel:
            self.channel.close()
            self.channel.unassign()
        if self.antnode:
            self.antnode.stop()

    def __enter__(self):
        return self

    def __exit__(self, type_, value, traceback):
        self.stop()

    def _start_antnode(self):
        stick = driver.USB2Driver(self.serial)
        self.antnode = node.Node(stick)
        self.antnode.start()

    def _setup_channel(self):
        key = node.NetworkKey('N:ANT+', self.netkey)
        self.antnode.setNetworkKey(0, key)
        self.channel = self.antnode.getFreeChannel()
        self.channel.name = 'C:HRM'
        self.channel.assign('N:ANT+', CHANNEL_TYPE_TWOWAY_RECEIVE)
        self.channel.setID(120, 0, 0)
        self.channel.setSearchTimeout(TIMEOUT_NEVER)
        self.channel.setPeriod(8070)
        self.channel.setFrequency(57)
        self.channel.open()

    def process(self, msg):
        time.sleep(5)  # 5秒の停止
        if isinstance(msg, message.ChannelBroadcastDataMessage):
            #print("heart rate is {}".format(ord(msg.payload[-1])))
            heart_rate = int(ord(msg.payload[-1]))
            self.call_func(heart_rate)

def my_print(data):
    print("heart rate is {}".format(data))

    # 心拍数をJSONファイルに書き込む    
    data_json = {"heart_rate": data}
    file = open("heart_rate.json", "w")
    json.dump(data_json , file)


if __name__ == '__main__':
    hrm = HeartRateMonitor(my_print)
    print("monitor start")
    hrm.start()
    print("monitor start listening")
    print("press Ctrl-C to stop this script")
    while True:
        try:
            time.sleep(1)
        except KeyboardInterrupt:
            print("monitor stop")
            hrm.stop()
            sys.exit(0)

Alexaスキルの作成

次にAlexaスキルを作成します。

スキル作成には、以下の書籍を参考にさせていただきました。
●スマートスピーカー × AIプログラミング ~自分でつくる人工知能 Amazon Echo、Google Home対応~

Alexaスキルの作成の詳細は省略します。
概要だけ記載します。

  1. スキルの呼び出し名を設定します。今回は「ジャービス」にしました
  2. インターフェースから「Audio Player」をonにします。これで音楽などを再生することができます
  3. インテントに、新たに「musicStart」を追加します。このインテントで音楽を再生します。
  4. サンプル発話に、「今の気分におすすめの音楽をかけて」と設定します
  5. これでビルドすれば、スキル作成完了です。Amazon Echoのアプリから「DEVスキル」をチェックし、作成したスキルを有効化します
  6. 最後にエンドポイントにラズベリーパイのアドレスを設定します。どのアドレスを設定すれば良いのかについては後ほど補足します

作成した内容のJSONは以下の通りです。
単純にインテントを新たにひとつ作成しただけになります。

JSONエディター
{
    "interactionModel": {
        "languageModel": {
            "invocationName": "ジャービス",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.PauseIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.ResumeIntent",
                    "samples": []
                },
                {
                    "name": "musicStart",
                    "slots": [],
                    "samples": [
                        "今の気分におすすめの音楽をかけて"
                    ]
                }
            ],
            "types": []
        }
    }
}

ラズベリーパイでAlexaスキルのエンドポイントを作成

最後にラズベリーパイ上でAlexaスキルのエンドポイントとなるプログラミングを実行し、自宅サーバーにします。

ポイントはAlexaと通信できるように、httpsでエンドポイントを公開する点です。そのためにngrokを使用しました。

ngrokを使うと、ローカルサーバーで立ち上げたプログラムに対して、外部からアクセスできるようになります。

最後にエンドポイントとなるプログラム、jarvis.pyをコーディングし、実行します。

この部分も、以下の書籍を参考にさせていただきました。
●スマートスピーカー × AIプログラミング ~自分でつくる人工知能 Amazon Echo、Google Home対応~

プログラムの内容は以下の通りです。

●jarvis.py

jarvis.py
# -*- coding: utf-8 -*-

from flask import Flask, render_template
from flask_ask import Ask, question, statement, audio
import json

app = Flask(__name__)
ask = Ask(app, '/')


# 起動時の対応
@ask.launch
def launched():
    return question('ジャービスです。いかがなさいましたか?')

# 音楽を再生
@ask.intent('musicStart')
def music_start():
    # 心拍のload
    file2 = open("heart_rate.json", "r")
    json_dict = json.load(file2)
    heart_rate = json_dict["heart_rate"]

    # 心拍の異常値処理
    if heart_rate > 200:
        heart_rate = 0

    # 条件分岐
    mean_heart_rate = 70
    if heart_rate > mean_heart_rate:
        out_index = 0
    else:
        out_index = 1

   # 出力の文章を作成
    speech_0 = '心拍数は{}です。'.format(heart_rate)

    speech_1 = [
        '普段の平均より高いです。落ち着いた曲を再生します', 
        '普段の平均より低いです。テンションが上がる曲を再生します'
    ]

    speech = speech_0 + speech_1[out_index]

    # 音楽URL
    stream_url = [
        "※音楽1のURLをここに書きます※", 
        "※音楽2のURLをここに書きます※"
    ]

    return audio(speech).play(stream_url[out_index])


@ask.intent('AMAZON.PauseIntent')
def stop():
    return audio("Jarvisを終了します").stop()


@ask.intent('AMAZON.StopIntent')
def stop():
    return audio("Jarvisを終了します").stop()


@ask.session_ended
def session_ended():
    return "{}", 200

if __name__ == '__main__':
    app.run(host="127.0.0.1", port=5000)

音楽1、2のURLには、流したい音楽のURLを記載します。
少し遊ぶだけなら、S3に音楽ファイルを置いて公開しておけば良いです。
今回はこのバージョンです。

ですがこの状態では音楽ファイルを他人に公開していることになるので、宜しくないです。
きちんとやる場合は、S3の「Pre-Signed URL」の機能を使用して、自分のAmazon Echoからのみアクセスできる認証付きURLを発行するようにします。

poseとstopのインテントに対しては、「ジャービスを終了します」と発言し、音楽を止めます。

コマンドラインからjarvis.pyを実行します。
これで、ローカルで127.0.0.1:5000にエンドポイントが立ち上がりました。

最後にngrokを実行し、127.0.0.1:5000に対応する、外部からのアクセスURLを取得します。

alexa_7.png

このアクセスURLをAlexaスキルのエンドポイントのURLに設定します。

以上で、完成です。

まとめ

Amazon Echo(Alexa)、スマートウォッチ、ラズベリーパイを使用して、今の気分におすすめの音楽をかけるシステムを作ってみました。

本当は心拍数に応じて、Amazon Music Unlimitedから音楽を再生したかったのですが、外部APIが公開されていませんでした。

Spotifyは開発APIがあるので、本格的に音楽を再生したい場合はSpotifyの使用が良いかもしれません。

以上、ご一読いただき、ありがとうございました。

画像引用:
http://www.garmin.co.jp/products/wearables/vivomove-hr-premium-silver/
https://leanpub.com/RPiMRE/read
http://liliankasem.com/2015/10/17/api-calls-using-httpclient-and-deserializing-json-in-c/
http://www.ysroad.net/shopnews/detail.php?bid=130293
https://opencampus.sh/events/prototyping-with-raspberry-pi/
https://www.raspberrypi.org/app/uploads/2017/05/Raspberry-Pi-3-hero-1-1571x1080.jpg