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()