RAPIRO(ラピロ)の音声制御 Ver. 2.0 その3

さて、前回までで音声制御による家電制御までこなしてくれるようになったラピロですが、これだけだとまだまだ使い方が限定されてしまうので、少なくとももう一つぐらい機能を付け加えたいところです。

というわけで、「天気について音声で質問したら、音声で教えてくれる」という機能を付け足したいと思います。

天気情報をラピロで取得するにあたっては、どこかでお天気情報を取得するためのWeb APIとかを公開してくれていたら、話としてはだいぶラクです。

少し探してみたところ、livedoorの提供しているWeather Hack APIが良さげな感じでしたので、これを使わせていただくことにします。個人利用の範囲なので、利用料金も掛かりません。

このあたりを参考にさせていただき、とりあえず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を見る飼い主の気持ちって、こんな感じなのかしら。