Alexaの組み込み2:AlexaPiを使ってみる

December 24, 2017

Alexaのクライアント機能を取り入れるため、今回はAlexaPiを使わせてもらいました(ありがとうございます)。インストール手順は、こちらのサイトを参考にさせていただきました。

AlexaPiにはブラウザで認証を行ってトークンを取得するためのauth_web.pyと、音声認識を行うためのmain.pyと言うプログラムが含まれています。auth_web.pyはトークンの取得に成功すれば、あとは使用しないっぽいです。main.pyは、音声の録音→認識を繰り返すもので、認識にはalexaのWEBAPIであるAVS APIを使っています。このAPIに録音した音声を送ると、応答音声も返ってくるみたいです。

ただ、このままでは英語でしか会話できないため、日本語で会話できるように変更することにしました。main.pyは古いAVS APIを使っているため、新しいバージョン(v20160207)のAPIを使うように変更しました。具体的には、認識用のAPIを呼ぶ前に、SettingsUpdatedイベントでロケールをja_JPに変更するように変更しました。その結果、無事に日本語で会話できるようになりました。変更後のmain.pyは以下の通りです。v20160207はHTTP2に対応しているようなので、hyperパッケージを使ってます。

#! /usr/bin/env python

import os
import random
import time
import RPi.GPIO as GPIO
import alsaaudio
import wave
import random
from creds import *
import requests
from hyper.contrib import HTTP20Adapter
import json
import re
import threading
from memcache import Client

# Settings
# button = 18 #GPIO Pin with button connected
# lights = [24, 25] # GPIO Pins with LED's conneted
device = "plughw:1,0"  # Name of your microphone/soundcard in arecord -L

# Setup
recorded = False
servers = ["127.0.0.1:11211"]
mc = Client(servers, debug=1)
path = os.path.realpath(__file__).rstrip(os.path.basename(__file__))

# audio input
audio = ""
inp = None


def internet_on():
    print "Checking Internet Connection"
    try:
        r = requests.get('https://api.amazon.com/auth/o2/token')
        init_alexa()
        print "Connection OK"
        return True
    except:
        print "Connection Failed"
        return False


def gettoken():
    token = mc.get("access_token")
    refresh = refresh_token
    if token:
        return token
    elif refresh:
        payload = {"client_id": Client_ID, "client_secret": Client_Secret,
                   "refresh_token": refresh, "grant_type": "refresh_token", }
        url = "https://api.amazon.com/auth/o2/token"
        r = requests.post(url, data=payload)
        resp = json.loads(r.text)
        mc.set("access_token", resp['access_token'], 3570)
        return resp['access_token']
    else:
        return False


def alexa():
    global audio
    # GPIO.output(24, GPIO.HIGH)
    # make session
    baseurl = 'https://avs-alexa-fe.amazon.com'
    s = requests.Session()
    s.mount(baseurl, HTTP20Adapter())

    # make request
    headers = {'Authorization': 'Bearer %s' % gettoken()}
    d = {
        "context": [
            {
                "header": {
                    "name": "PlaybackState",
                    "namespace": "AudioPlayer",
                },
                "payload": {
                    "token": "",
                    "offsetInMilliseconds": "0",
                    "playerActivity": "IDLE"
                }
            }
        ],
        "event": {
            "header": {
                "namespace": "SpeechRecognizer",
                "name": "Recognize",
                        "messageId": "messageId-123",
                        "dialogRequestId": "dialogRequestId-321"
            },
            "payload": {
                "profile": "CLOSE_TALK",
                "format": "AUDIO_L16_RATE_16000_CHANNELS_1"
            }
        }
    }
    with open(path + 'recording.wav') as inf:
        files = [
            ('file', ('metadata', json.dumps(d), 'application/json; charset=UTF-8')),
            ('file', ('audio', inf, 'application/octet-stream'))
        ]
        r = s.post(baseurl + '/v20160207/events', headers=headers, files=files)
    if r.status_code == 200:
        for v in r.headers['content-type'].split(";"):
            if re.match('.*boundary.*', v):
                boundary = v.split("=")[1]
        data = r.content.split(boundary)
        for d in data:
            if (len(d) >= 1024):
                audio = d.split('\r\n\r\n')[1].rstrip('--')
        with open(path + "response.mp3", 'wb') as f:
            f.write(audio)
        # GPIO.output(25, GPIO.LOW)
        os.system('mpg123 -q {}1sec.mp3 {}response.mp3'.format(path, path))
        # GPIO.output(24, GPIO.LOW)
    else:
        # GPIO.output(lights, GPIO.LOW)
        for x in range(0, 3):
            time.sleep(.2)
            # GPIO.output(25, GPIO.HIGH)
            time.sleep(.2)
            # GPIO.output(lights, GPIO.LOW)


def init_alexa():
    # make session
    baseurl = 'https://avs-alexa-fe.amazon.com'
    s = requests.Session()
    s.mount(baseurl, HTTP20Adapter())

    # make request
    headers = {'Authorization': 'Bearer %s' % gettoken()}
    d = {
        "event": {
            "header": {
                "namespace": "Settings",
                "name": "SettingsUpdated",
                "messageId": "msg123"
            },
            "payload": {
                "settings": [
                    {
                        "key": "locale",
                        "value": "ja-JP"
                    }
                ]
            }
        }
    }
    files = [
        ('file', ('metadata', json.dumps(d), 'application/json; charset=UTF-8'))
    ]
    r = s.post(baseurl + '/v20160207/events', headers=headers, files=files)
    if r.status_code not in [200, 204]:
        raise RuntimeError(r.text)

# need to use this with thread.


def async_recording():
    global recorded
    time.sleep(5)
    recorded = False


def start():
    global inp
    global audio
    global recorded
    while True:
        if inp == None:
            inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE,
                                alsaaudio.PCM_NORMAL, device)
            inp.setchannels(1)
            inp.setrate(16000)
            inp.setformat(alsaaudio.PCM_FORMAT_S16_LE)
            inp.setperiodsize(500)
            audio = ""
            recorded = True

        os.system('mpg123 -q {}record_now.mp3'.format(path))

        # recording(asyncronous)
        recording_thread = threading.Thread(target=async_recording)
        recording_thread.start()

        while recorded == True:
            l, data = inp.read()
            if l:
                audio += data
        recording_thread = None

        # os.system('arecord -d 3 -D {} {}recording.wav'.format(device,path))
        os.system('mpg123 -q {}request_now.mp3'.format(path))

        # call alexa
        rf = open(path + 'recording.wav', 'w')
        rf.write(audio)
        rf.close()
        inp = None

        # os.system('aplay {}recording.wav'.format(path))

        alexa()

        time.sleep(10)

if __name__ == "__main__":
    while internet_on() == False:
        print "."
    token = gettoken()
    os.system('mpg123 -q {}1sec.mp3 {}hello.mp3'.format(path, path))
    start()