RAPIRO(ラピロ)の音声制御 Ver. 2.0 その3
さて、前回までで音声制御による家電制御までこなしてくれるようになったラピロですが、これだけだとまだまだ使い方が限定されてしまうので、少なくとももう一つぐらい機能を付け加えたいところです。
というわけで、「天気について音声で質問したら、音声で教えてくれる」という機能を付け足したいと思います。
天気情報をラピロで取得するにあたっては、どこかでお天気情報を取得するためのWeb APIとかを公開してくれていたら、話としてはだいぶラクです。
少し探してみたところ、livedoorの提供しているWeather Hack APIが良さげな感じでしたので、これを使わせていただくことにします。個人利用の範囲なので、利用料金も掛かりません。
- Weather Hacks お天気Webサービス仕様(本家)
- livedoorの天気予報API「Weather Hacks」を使ってみた
- 『Weather Hacks』のRSSデータ配信が停止 1次細分区(cityタグ)の新旧対応表を作ってみた
このあたりを参考にさせていただき、とりあえずAPI利用サンプルをPythonで書いてみました。
#!/usr/bin/env python # -*- coding: utf-8 -*- import requests location_id = 270000 weather_data = requests.get('http://weather.livedoor.com/forecast/webservice/json/v1?city=%s' % location_id).json() weather_today = weather_data['forecasts'][0]['telop'] weather_today_min = weather_data['forecasts'][0]['temperature']['min'] if weather_today_min != None: weather_today_min = weather_data['forecasts'][0]['temperature']['min']['celsius'] weather_today_max = weather_data['forecasts'][0]['temperature']['max'] if weather_today_max != None: weather_today_max = weather_data['forecasts'][0]['temperature']['max']['celsius'] weather_tomorrow = weather_data['forecasts'][1]['telop'] weather_tomorrow_min = weather_data['forecasts'][1]['temperature']['min'] if weather_tomorrow_min != None: weather_tomorrow_min = weather_data['forecasts'][1]['temperature']['min']['celsius'] weather_tomorrow_max = weather_data['forecasts'][1]['temperature']['max'] if weather_tomorrow_max != None: weather_tomorrow_max = weather_data['forecasts'][1]['temperature']['max']['celsius'] weather_description = weather_data['description']['text'].replace('\n','').replace('\r','') #print weather_data['title'] #print weather_today #print weather_today_min #print weather_today_max #print weather_tomorrow #print weather_tomorrow_min #print weather_tomorrow_max #print weather_description message = "今日の天気は%sです。" % weather_today.encode('utf-8') if weather_today_max != None: message += "最高気温は%s度です。" % weather_today_max.encode('utf-8') if weather_today_min != None: message += "最低気温は%s度です。" % weather_today_min.encode('utf-8') message += "明日の天気は%sです。" % weather_tomorrow.encode('utf-8') if weather_tomorrow_max != None: message += "最高気温は%s度です。" % weather_tomorrow_max.encode('utf-8') if weather_tomorrow_min != None: message += "最低気温は%s度です。" % weather_tomorrow_min.encode('utf-8') message += weather_description.encode('utf-8') print message
location_idを変更すれば、好きなところの天気を取得できるはずです。
あとは、これを前回作ったJuliusの待ち受け用プログラムに組み込んでやります。ちょっと長くなりますが、こんな感じです。
# coding: utf-8 import socket import serial import xml.etree.ElementTree as ET import requests import time import threading host = 'localhost' port = 10500 LOCATION_ID = 270000 COMMAND_READY_DURATION = 10 COMMAND_EXECUTING_DURATION = 30 STATUS_WAITING = 0 STATUS_READY = 1 STATUS_EXECUTING = 2 STATUS_FINISH = 3 status = STATUS_WAITING def status_change(new_status): global status if status == STATUS_WAITING: if new_status == STATUS_READY: status = new_status print "Status is READY." threading.Timer(COMMAND_READY_DURATION, status_change, args=[STATUS_WAITING]).start() elif status == STATUS_READY: if new_status == STATUS_WAITING: status = new_status print "Status is WAITING." res = requests.get('http://192.168.24.50:10080/v1/robots/rapiro/control/init') print 'Julius Server Received from Control Server:%d' % res.status_code elif new_status == STATUS_EXECUTING: status = new_status print "Status is EXECUTING." threading.Timer(COMMAND_EXECUTING_DURATION, status_change, args=[STATUS_FINISH]).start() elif status == STATUS_EXECUTING: if new_status == STATUS_WAITING: print "Command is executed now, so I do NOT change status." if new_status == STATUS_FINISH: status = STATUS_WAITING print "Status is WAITING." res = requests.get('http://192.168.24.50:10080/v1/robots/rapiro/control/init') print 'Julius Server Received from Control Server:%d' % res.status_code TODAY = 0 TOMORROW = 1 def get_weather(target_day): weather_data = requests.get('http://weather.livedoor.com/forecast/webservice/json/v1?city=%s' % LOCATION_ID).json() weather_today = weather_data['forecasts'][0]['telop'] weather_today_min = weather_data['forecasts'][0]['temperature']['min'] if weather_today_min != None: weather_today_min = weather_data['forecasts'][0]['temperature']['min']['celsius'] weather_today_max = weather_data['forecasts'][0]['temperature']['max'] if weather_today_max != None: weather_today_max = weather_data['forecasts'][0]['temperature']['max']['celsius'] weather_tomorrow = weather_data['forecasts'][1]['telop'] weather_tomorrow_min = weather_data['forecasts'][1]['temperature']['min'] if weather_tomorrow_min != None: weather_tomorrow_min = weather_data['forecasts'][1]['temperature']['min']['celsius'] weather_tomorrow_max = weather_data['forecasts'][1]['temperature']['max'] if weather_tomorrow_max != None: weather_tomorrow_max = weather_data['forecasts'][1]['temperature']['max']['celsius'] weather_description = weather_data['description']['text'].replace('\n','').replace('\r','') print weather_description weather_descriptions = weather_description.encode('utf-8').split('。') #print weather_descriptions[0] #print weather_descriptions[1] #print weather_descriptions[2] message = '' if target_day == TODAY: message += "今日の天気は%sです。" % weather_today.encode('utf-8') if weather_today_max != None: message += "最高気温は%s度です。" % weather_today_max.encode('utf-8') if weather_today_min != None: message += "最低気温は%s度です。" % weather_today_min.encode('utf-8') message += weather_descriptions[1] + '。' if weather_descriptions[2].startswith("午後"): message += weather_descriptions[2] + '。' if target_day == TOMORROW: message += "明日の天気は%sです。" % weather_tomorrow.encode('utf-8') if weather_tomorrow_max != None: message += "最高気温は%s度です。" % weather_tomorrow_max.encode('utf-8') if weather_tomorrow_min != None: message += "最低気温は%s度です。" % weather_tomorrow_min.encode('utf-8') for i in [2,3]: if "明日" in weather_descriptions[i]: message += weather_descriptions[i] + '。' return message clientsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) clientsock.connect((host, port)) print("Connected Julius Server.") while True: recv_data = clientsock.recv(512) # juliusの区切り文字で分割 sp = recv_data.split('.\n') #print(sp) for elem in sp: if(elem != ''): try: root = ET.fromstring(elem) for word in root.iter('WHYPO'): keyword = word.get('WORD') print(keyword) if keyword.startswith(u'ラピロ'): if status == STATUS_WAITING: print "RAPIRO is ready to your command." status_change(STATUS_READY) res = requests.get('http://192.168.24.50:10080/v1/robots/rapiro/control/talk?text=' + keyword + '&face=happy&emotion=happy') print 'Julius Server received from Control Server:%d' % res.status_code elif keyword in [u'おやすみなさい', u'おやすみ', u'おはよう', u'電気消して', u'電気点けて']: if status == STATUS_READY: print "RAPIRO got command:" + keyword status_change(STATUS_EXECUTING) res = requests.get('http://192.168.24.50:10080/v1/robots/rapiro/control/talk?text=' + keyword + '&face=happy&emotion=happy') print 'Julius Server Received from Control Server:%d' % res.status_code elif keyword in [u'今日の天気は?', u'明日の天気は?', u'明日は晴れかな', u'明日は雨かな', u'天気を教えて',u'今日の天気を教えて',u'明日の天気を教えて']: if status == STATUS_READY: target_day = TODAY if keyword.startswith(u"明日"): target_day = TOMORROW weather = get_weather(target_day) print "RAPIRO got command:" + keyword status_change(STATUS_EXECUTING) res = requests.get('http://192.168.24.50:10080/v1/robots/rapiro/control/talk?text=' + weather + '&duration=long') print 'Julius Server Received from Control Server:%d' % res.status_code except: print("Failed to parse")
Weather Hack APIが返してくれているテキスト情報は、最終的にHOYAのVoiceText Web APIに渡して音声合成させる形になる(*上記のソースコードの先の話)のですが、テキスト情報をすべて喋らせようと思うと、無料版の文字数制限(200文字)に引っかかってしまうので、喋らせたい内容を取捨選択する必要があります。Weather Hack APIの返してくるテキスト情報のパターンをまだ十分把握していないので、とりあえずの実装になっています。
ということで、実際に喋らせてみた結果が冒頭の動画になります。「南海上の前線」を「なんかいかみのまえせん」と読み上げてしまっているのは、ご愛嬌ということで。
しかしこれまた、音声の認識率が大変悪い。。。明日の天気を聞いているのに、何度も今日の天気を教えてくれる、というのを何回繰り返したことか。
でも実は、Juliusを単独で動かしているときはそこまで認識がひどいわけではなく、そこそこ認識してくれます。動作制御Webサーバやら音声発話Webサーバやらと並行して動かすと、ものすごく認識率が落ちるという。うーん、そんなことあるのかしら?CPUに負荷がかかり過ぎている?そろそろラピロの頭脳をRaspberry Pi 2に入れ替えなきゃいけない時が来たということかしら。。。
あと、ラピロのサーボが全体的にギシギシ言い出しているのがかなり心配。表情と声は出せるようにしているから、最悪、各関節は固定で、コミュニケーション用途に特化させるというのも考えないといけないかな。。。うー、年老いてきたAIBOを見る飼い主の気持ちって、こんな感じなのかしら。
ディスカッション
ピンバック & トラックバック一覧
[…] 天気予報の外部Webサービスですが、過去に一度使ったことのある、Weather Hacksを使います。Web APIでアクセスできて結果もJSONで返ってくるので、扱いやすいのです。Web APIのアクセス方法と […]