Raspberry Pi から IRKitを操作するための第一歩
Python3 で IRKitを操作するプログラムに挑戦中
勉強を兼ねて Raspberry Pi から IRKitを操作するプログラムを Python3で作ってみようと思っています。とは言え、既に先人達が色々作ってくれていますから、それらを殆ど丸パクリした感もありますが・・・
第一歩は無線LANに繋がっている IRKitデバイスを Bonjour(ボンジュール)で見つけ出すことからです。手順は以下のようになります。
- libdnssdをインストールする
- Python3用の pybonjourモジュールをインストールする
- pybonjour の Exampleプログラムを流用して IRKitデバイスのIPアドレスを求めるプログラムを作る
1. libdnssdをインストールする
ウチの Raspberry Pi2 には Avahi(オープンソースによる Bonjour)がインストール済みなのですが、libdnssdライブラリは別途インストールする必要があるようなので、先ずはこれをインストールしました。
$ sudo apt-get install libavahi-compat-libdnssd-dev
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています
状態情報を読み取っています... 完了
以下の特別パッケージがインストールされます:
libavahi-client-dev libavahi-common-dev libavahi-compat-libdnssd1 libdbus-1-dev
以下のパッケージが新たにインストールされます:
libavahi-client-dev libavahi-common-dev libavahi-compat-libdnssd-dev libavahi-compat-libdnssd1 libdbus-1-dev
〜略〜
libavahi-compat-libdnssd1:armhf (0.6.31-2) を設定しています ...
libavahi-common-dev (0.6.31-2) を設定しています ...
libdbus-1-dev (1.6.8-1+deb7u6) を設定しています ...
libavahi-client-dev (0.6.31-2) を設定しています ...
libavahi-compat-libdnssd-dev (0.6.31-2) を設定しています ...
|
2. Python3用の pybonjourモジュールをインストールする
Pythonから Bonjourを扱えるようにするための pybonjour というモジュールをインストールします。実はここで Python2系用の pybonjourをインストールするという失敗をやらかしてしまったので、その時の様子を備忘録として書いておこうと思います。
Pythonの Bonjour実装をググったところ、最初に見つかったのがここでした。実はこれは Python2系用なのですが、その時には気付かなかったのでした。
$ git clone https://github.com/nickstenning/pybonjour.git
Cloning into 'pybonjour'...
remote: Counting objects: 162, done.
remote: Total 162 (delta 0), reused 0 (delta 0), pack-reused 162
Receiving objects: 100% (162/162), 184.05 KiB, done.
Resolving deltas: 100% (77/77), done.
$ cd pybonjour
$ sudo python3 setup.py install
Traceback (most recent call last):
File "setup.py", line 32, in
import pybonjour
File "/home/nai/work/pybonjour/pybonjour/pybonjour.py", line 823, in
_create_function_bindings()
File "/home/nai/work/pybonjour/pybonjour/pybonjour.py", line 806, in _create_function_bindings
for name, (restype, errcheck, outparam, argtypes) in specs.iteritems():
AttributeError: 'dict' object has no attribute 'iteritems'
|
と言う具合に、モジュールをダウンロードしてインストールしようとしたところエラーが出て失敗してしまいました。エラーの内容は 'dict'(辞書)オブジェクトの属性には 'iteritems' が無いよ!と言うもの。これについて調べたところ、Python2系と3系の非互換で、Python3では dict.iterkeys(), dict.itervalues(), dict.iteritems() メソッドが廃止されたとのこと。これらの代わりに dict.keys(), dict.values(), dict.items() を使えば良いらしい。今回エラーが出ているのは dict.iteritems() メソッドだけど、厳密に言えば Python2 の dict.iteritems() メソッドは辞書オブジェクト要素のイテレータ(反復子)を返すのに対し、Python3 の dict.items() メソッドはview(ビュー)と呼ばれる軽量なコンテナーオブジェクトを返すとの事。viewはイテレータのようにも振舞うので、単純に dict.iteritems() → dict.iterms() に変えても殆どの場合問題にはならないと言う。ナルホド、良く分からん(^^;)。
とにかく単純にエラーになっている iteritems を items に書き換えてみた(問題の箇所は "pybonjour.py" ファイルの 806行目と 1944行目)ところ、
$ sudo python3 setup.py install
running install
running build
running build_py
running install_lib
copying build/lib/pybonjour.py -> /usr/local/lib/python3.2/dist-packages
byte-compiling /usr/local/lib/python3.2/dist-packages/pybonjour.py to pybonjour.cpython-32.pyc
running install_egg_info
Writing /usr/local/lib/python3.2/dist-packages/pybonjour-1.1.1.egg-info
|
インストールできてしまいました。ところが、その後よく探してみたらPython3用の pybonjourがちゃんとある事が分かりました。と言うことで、入れ替えるため先にインストールした pybonjourをアンインストールします。アンインストールするためには、もう一度インストールを行ってインストールされたファイルを記録する必要があるそうで、ちょっと面倒臭い。
$ sudo python3 setup.py install --record files.txt
running install
running build
running build_py
running install_lib
running install_egg_info
Removing /usr/local/lib/python3.2/dist-packages/pybonjour-1.1.1.egg-info
Writing /usr/local/lib/python3.2/dist-packages/pybonjour-1.1.1.egg-info
writing list of installed files to 'files.txt'
$ su
# cat files.txt | xargs rm -rvf
`/usr/local/lib/python3.2/dist-packages/pybonjour.py' を削除しました
`/usr/local/lib/python3.2/dist-packages/__pycache__/pybonjour.cpython-32.pyc' を削除しました
`/usr/local/lib/python3.2/dist-packages/pybonjour-1.1.1.egg-info' を削除しました
# exit
$ cd ..
$ rm -rf pybonjour
|
その後、Python3用の pybonjourをインストールします。
$ git clone https://github.com/depl0y/pybonjour-python3.git
Cloning into 'pybonjour-python3'...
remote: Counting objects: 16, done.
remote: Total 16 (delta 0), reused 0 (delta 0), pack-reused 16
Unpacking objects: 100% (16/16), done.
$ cd pybonjour-python3
$ sudo python3 setup.py install
running install
running build
running build_py
running install_lib
copying build/lib/pybonjour.py -> /usr/local/lib/python3.2/dist-packages
byte-compiling /usr/local/lib/python3.2/dist-packages/pybonjour.py to pybonjour.cpython-32.pyc
running install_egg_info
Writing /usr/local/lib/python3.2/dist-packages/pybonjour-1.1.1.egg-info
|
今度はエラーが起きることなくインストールできました。
3. pybonjour の Exampleプログラムを流用して IRKitデバイスのIPアドレスを求めるプログラムを作る
上でダウンロードした pybonjour-python3 配下にある examplesディレクトリ内にサンプルプログラムがあります。3本あるサンプルプログラムのうち "browse_and_resolve.py" を流用して IRKitの IPアドレスを求めるプログラムを作りました。しかし、サンプルプログラムは print が関数ではないところを見ると Python2系用みたいですね。まぁ print以外は Python3でも問題ないようですけど。
参考にさせてもらったのが こちらの "resolve.py" です。ウチの環境では、このプログラムのままでは IRKitを見つけてくれませんでしたので、少々手直ししたり単純化したりしました。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import select
import sys
import pybonjour
from socket import gethostbyname
regtype = '_irkit._tcp'
timeout = 5
resolved = []
irkits = []
def resolve_callback(sdRef, flags, interfaceIndex, errorCode, fullname,
hosttarget, port, txtRecord):
if errorCode == pybonjour.kDNSServiceErr_NoError:
irkits.append((hosttarget, port))
resolved.append(True)
def browse_callback(sdRef, flags, interfaceIndex, errorCode, serviceName,
regtype, replyDomain):
if errorCode != pybonjour.kDNSServiceErr_NoError:
return
if not (flags & pybonjour.kDNSServiceFlagsAdd):
print('Service removed', file=sys.stderr)
return
with pybonjour.DNSServiceResolve(0,
interfaceIndex,
serviceName,
regtype,
replyDomain,
resolve_callback) as resolve_sdRef:
while not resolved:
ready = select.select([resolve_sdRef], [], [], timeout)
if resolve_sdRef not in ready[0]:
print('Resolve timed out', file=sys.stderr)
break
pybonjour.DNSServiceProcessResult(resolve_sdRef)
else:
resolved.pop()
def resolve_irkits():
with pybonjour.DNSServiceBrowse(regtype = regtype,
callBack = browse_callback) as browse_sdRef:
while True:
ready = select.select([browse_sdRef], [], [], timeout)
if browse_sdRef in ready[0]:
pybonjour.DNSServiceProcessResult(browse_sdRef)
else:
break
if __name__ == '__main__':
resolve_irkits()
for irkit in irkits:
print('hosttarget={0}
IPaddress={1}
port={2}'.format(irkit[0], gethostbyname(irkit[0]), irkit[1]))
|
殆ど変わってませんけどね。変えた所は主に resolve_irkits関数の中で、"browse_and_resolve.py"サンプルプログラムと同様に selectしながらループさせてます。こうしないとうまく IRKitを見つけてくれないようです。サンプルプログラムでは^Cで打ち切るまで無限ループさせていたところを、応答が帰って来なくなったらループを抜けるようにしました(したつもり ^^;)。
これを実行した結果が以下です。
$ ./resolve_irkit.py
hosttarget=irkit648a.local.
IPaddress=192.168.0.6
port=80
|
これで無線LANに繋がっている IRKitのIPアドレスを求めることができるようになりました。次のステップは IRKit のAPI(HTMLコマンド)でリモコン信号のやり取りができるようにすることですけど、このペースだといつになるかなぁ・・・
|