Raspberry PiとSiriで家電の音声制御 前編 Web APIでの家電制御

 th_siri-1

 

前回お話しした通りで、Raspberry PiとSiriを使った家電制御ということで、まずは制御される側のデバイスの方を先に用意してしまいます。具体的には、赤外線リモコンデバイスであるirMagicianをWeb APIで制御できるようにします。

siri-raspberry-2

この部分ですね。これについては、実は過去に取り組み済みです。ただ、この時はRaspberry Pi Type B+とPython 2.7の環境だったのに対し、今回の環境はRaspberry Pi 2 Type BとPython 3.5なので、情報のアップデートも兼ねて、改めて記事として書いておきます。

現状のPythonは2系と3系の両方が現役で、Raspbianのデフォルトでは2.7になっています(2016/3時点)。個人的には「2系じゃないとダメ」というシーンはだいぶなくなってきていると思うし、今後のことも考慮して、自力で3.5系に上げました。そのやり方については、過去に書いた記事をご参照ください。

以下、こちらの手順でPython 3.5をデフォルト設定にした前提で話を進めます。bottleフレームワークをまだインストールしていない場合は、ここでインストールします。

$ easy_install -U bottle

それから、irMagicianとPythonの通信に必要なpyserial、Raspberry PiからHTTPリクエストを発行するときに便利なrequetsもここで一緒にインストールしておきます。

$ pip install pyserial
$ pip install requests

ちなみに、各モジュールがインストール済みであるかどうかは以下で確認できます。

$ pip freeze
bottle==0.12.9
pyserial==3.0.1
requests==2.9.1

 

ではでは、irMagicianのプログラムの作成に入ります。とりあえず適当なディレクトリを作って、そこで作業していきます。

$ mkdir -p server/python
$ cd server/python
$ pwd
/home/pi/server/python

 サンプルプログラムについては、以下で公開してくださっているirm.pyを利用させて頂くのが良いと思います。

Python 3系で上記のサンプルファイルを動かす場合は、以下の修正が必要になります。Python 2系ならそのままでOK。

  • print文をカッコつきのprint()に修正
  • Unicode文字列をByte文字列に変換するため、ir_serial.write(“…”)のようになっている部分を全てir_serial.write(b“…”)のように修正

修正が済んだら、irMagicianの動作テストです。

$ python irm.py -c
Capturing IR...
b'... Time Out !\r\n'

こんな表示が出たら、もう一度同じコマンドを実行して、タイムアウトになる前に覚えさせたいリモコンのボタンを押します。

$ python irm.py -c
Capturing IR...
b'... 252\r\n'

こんな感じで”Time Out!”の代わりに数字が表示されればOKです。早速再生してみましょう。

$ python irm.py -p
Playing IR...
b'... Done !\r\n'

これでおそらく機器が制御できるはずです。うまくいかない場合は、最初はirMagicianをできるだけ対象機器に近づけてからテストすると良いと思います。また、エアコンのように複雑な信号のものでなく、照明とか扇風機とかシンプルなもので試した方が良いかと思います。

うまくいったら、現在記憶している信号情報をファイルに書き出して、いつでも使えるようにしておきます。

$ python irm.py -s -f room_t_light_on.json
Saving IR data to room_t_light_on.json ...
Done !

ファイルから信号を再生できるかどうかのテストです。

$ python irm.py -p -f room_t_light_on.json
Playing IR with room_t_light_on.json ...
b'... Done !\r\n'

これでirMagician自体のテストは完了です。同じ要領で、必要なだけ機器の制御信号を学習してファイルに書き出しておきましょう。ここではとりあえず照明のON/OFFの2つ分のファイルを作っておきました。今後ファイルが増える可能性があるので、適当にフォルダを作ってそこで管理しておきます。

$  pwd
/home/pi/server/python
$ mkdir ir_data
$ mv *.json ir_data/.
$ ls ir_data/
room_t_light_off.json  room_t_light_on.json

 

さて、次はbottleフレームワークを使ってWeb APIで制御できるようにしましょう。ソースコードはこんな感じになります。

from bottle import route, run, request, response, hook
import serial
import time
import json
import os

ir_serial = serial.Serial("/dev/ttyACM0", 9600, timeout = 1)
IR_DATA_DIR = "/home/pi/server/python/ir_data"

def playIR(path):
  if path and os.path.isfile(path):
    print("Playing IR with %s ..." % path)
    f = open(path)
    data = json.load(f)
    f.close()
    recNumber = len(data['data'])
    rawX = data['data']

    ir_serial.write(b"n,%d\r\n" % recNumber)
    ir_serial.readline()

    postScale = data['postscale']
    ir_serial.write(b"k,%d\r\n" % postScale)
    #time.sleep(1.0)
    msg = ir_serial.readline()
    #print(msg)

    for n in range(recNumber):
        bank = n / 64
        pos = n % 64
        if (pos == 0):
          ir_serial.write(b"b,%d\r\n" % bank)

        ir_serial.write(b"w,%d,%d\n\r" % (pos, rawX[n]))

    ir_serial.write(b"p\r\n")
    msg = ir_serial.readline()
    print(msg)
    #ir_serial.close()
  else:
    print("Playing IR...")
    ir_serial.write(b"p\r\n")
    time.sleep(1.0)
    msg = ir_serial.readline()
    print(msg)

def search_ir_data(room, device, function, parameter):
  path = IR_DATA_DIR
  if room == "room_t":
    if device == "light":
      if function == "power":
        if parameter == "on":
          path += "/room_t_light_on.json"
        elif parameter == "off":
          path += "/room_t_light_off.json"
  return path

@hook('after_request')
def enable_cors():
  response.content_type = 'application/json'

def appliances_response_json(room, device, function, parameter, result):
  obj = {'room':room, 'device':device, 'function':function, 'parameter':parameter, 'result':result}
  return json.dumps(obj)

@route('/appliances/myhome')
def control_appliances():
  room      = request.query.room.lower()
  device    = request.query.device.lower()
  function  = request.query.function.lower()
  parameter = request.query.parameter.lower()
  print("Room:" + room + ", Device:" + device + " , Function:" + function + ", Parameter:" + parameter)
  path = search_ir_data(room, device, function, parameter)
  print("IR Data Path: " + path)
  result = ''
  if path == IR_DATA_DIR:
    print("Failed to control.")
    result = 'failure'
  else:
    playIR(path)
    result = 'success'
  return appliances_response_json(room, device, function, parameter, result)

run(host='192.168.24.204', port=10080, debug=True)

指定したパラメータと制御の結果をJSONで返すようにしています。また、最後のIPアドレスは、事前にRaspberry Piに割り当てた固定IPアドレスです。

ではでは、実行してみましょう。

$ python control_server.py 
Bottle v0.12.9 server starting up (using WSGIRefServer())...
Listening on http://192.168.24.204:10080/
Hit Ctrl-C to quit.

この状態で、同じローカルネット上のPCのブラウザで、URL欄に”http://192.168.24.204:10080/appliances/myhome?room=room_t&device=light&function=power&parameter=off”のように入力すると、irMagicianから赤外線信号が送信されて、以下のように結果が返ってきます。

ir_success

“result”が”success”になってますね。ここでURLのparameterを間違えて”off”じゃなくて”of”にしてしまうと、

ir_failure

こんな感じで”result”が”failure”になって返ってきます。もちろん赤外線信号は送信されません。

 

こんな感じで、制御用のWeb APIを持った家電機器の出来上がりです。とてもシンプル。

最後に、Raspberry Piを起動した時に自動的にこのプログラムが立ち上がるように設定しておきます。

$ sudo vim /etc/rc.local 

自分はエディタとしてvimを使っていますが、お好きなエディタを使って、以下の”sudo …”で始まる一文を”exit 0″の前に追加してください。

...
# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi

sudo -i -u pi python /home/pi/server/python/control_server.py &

exit 0

“sudo -i -u pi”で、”piユーザのログインシェルで後のコマンドを実行してね”という指定になります。自分のPython 3.5の設定はpiユーザだけで有効になるようになっているので、わざわざこんな指定をしなくちゃいけません。。。。うーん、Python 3.5にこだわったせいで余計な手間が増えてるなあ。特にこだわりがなければ、Raspbianのデフォルトの2.7系で進めた方が早いと思います。

 

th_siri-1

ちなみに、irMagicianを取り付けたRaspberry Piはこんな感じになっています。固定式のフレキシブルUSBケーブルを使うと良い感じで固定できます。irMagicianはそのままだと夜中にLEDがピカピカ光ってまぶしいので、3Dプリンタで適当に作ったカバーを被せています。また、別機能ですがAirPlayにも対応しているので、USBオーディオとスピーカーも接続しています。

 

次回はいよいよキモである、Siri対応に取り組みます。