手元に古いパソリがあったため、以下を参考に、Raspberry Piとパソリ RC-S320を使ってFeliCaを読んでみました。
Rasberry piでfelica(edy/suica)を読む - いろいろつまみ食い
環境
開発PC
- Windows7
- パソリ RC-S320
- Visual Studio 2013 Community
- PTVS 2.1.0
RaspberryPi
- Raspberry Pi 2 Model B
- Raspbian 2015-02-16
- Python 2.7.3
- libpafe 0.0.8
FeliCa読み取り用ライブラリの調査
Pythonのライブラリを探してみたところ、nfcpy
がありました。
とても良さそうでしたが、Supported Hardwareを確認したところ、
http://nfcpy.readthedocs.org/en/latest/overview.html#supported-hardware
と、手元のRS-320はサポート外のようでした。
そのため、Pythonに限定しないでライブラリがないかを探してみたところ、libpafe
がありました。
libpafe
上記の参考記事にもある通り、Raspberry Piでの動作も大丈夫そうだったので、libpafeを使うことにしました。
あとはlibpafeをどの言語から扱うかを考えたところ、libpafe-ruby
がありました。
libpafe-ruby
Ruby2.1系でも動作しているようでした。
初心者歓迎詐欺被害者の会: Rubyでrequire 'pasori' したい話
ただRaspberry Piなので、Pythonで扱う方法がないかを調べたところ、Python標準ライブラリのctypes
を使えばC言語のライブラリを扱えそうでした。
15.18. ctypes — Pythonのための外部関数ライブラリ — Python 2.7ja1 documentation
以上より、libpafe + ctypesでFeliCaを読むことにしました。
確認と準備
ディレクトリの作成
Raspberry Pi上にディレクトリを作っておきます。
pi@raspberrypi ~ $ ls python_games pi@raspberrypi ~ $ mkdir pasori pi@raspberrypi ~ $ cd pasori
デフォルトのRaspbianでRC-S320が認識されているか確認
Raspbianを起動後、USBにRC-S320を挿入し、lsusb
で状況を確認します。
pi@raspberrypi ~/pasori $ lsusb Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. Bus 001 Device 004: ID 054c:01bb Sony Corp. FeliCa S320 [PaSoRi]
デフォルトのままでも認識されているようです。
libpafeのインストール
ダウンロードと解凍
pi@raspberrypi ~/pasori $ wget http://homepage3.nifty.com/slokar/pasori/libpafe-0.0.8.tar.gz ... 2015-06-** **:**:** (703 KB/s) - `libpafe-0.0.8.tar.gz' saved [345264/345264] pi@raspberrypi ~/pasori $ tar xzvf libpafe-0.0.8.tar.gz ... libpafe-0.0.8/Makefile.in
インストール
デフォルトのままで./configure
パッケージが無いエラーが出ました。
pi@raspberrypi ~/pasori $ cd libpafe-0.0.8/ pi@raspberrypi ~/pasori/libpafe-0.0.8 $ ./configure ... configure: error: Package requirements (libusb >= 0.1.12) were not met: No package 'libusb' found Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables LIBUSB_CFLAGS and LIBUSB_LIBS to avoid the need to call pkg-config.See the pkg-config man page for more details.
パッケージの確認
libusbの1.0系はdevがあるようですが、0.1系がdevがなく、これが原因でエラーが出たようです。
pi@raspberrypi ~/pasori $ sudo apt-cache search libusb* ... libusb-0.1-4 - userspace USB programming library libusb-1.0-0 - userspace USB programming library libusb-1.0-0-dev - userspace USB programming library development files ...
libusbパッケージのインストール
pi@raspberrypi ~/pasori/libpafe-0.0.8 $ sudo apt-get install libusb-dev ... Setting up libusb-dev (2:0.1.12-20+nmu1) ...
再度、./configure
成功しました。
pi@raspberrypi ~/pasori/libpafe-0.0.8 $ ./configure ... config.status: executing libtool commands
make
pi@raspberrypi ~/pasori/libpafe-0.0.8 $ make ... make[1]: Leaving directory '/home/pi/pasori/libpafe-0.0.8'
make install
pi@raspberrypi ~/pasori/libpafe-0.0.8 $ sudo make install ... Libraries have been installed in: /usr/local/lib ... make[1]: Leaving directory '/home/pi/pasori/libpafe-0.0.8'
インストール先の確認
Pythonのctypes
で使うlibpafe.so
が見つかりました。
pi@raspberrypi /usr/local/lib $ ls libpafe.a libpafe.so libpafe.so.0.0.8 python3.2 libpafe.la libpafe.so.0 python2.7 site_ruby
動作確認
付属のプログラムを実行して、問題なく動作することを確認します。
pi@raspberrypi ~/pasori/libpafe-0.0.8 $ sudo ./tests/pasori_test PaSoRi (RC-S320) firmware version 1.40 Echo test... success EPROM test... success RAM test... success CPU test... success Polling test... success
udevの設定
udevの設定を行います。
pi@raspberrypi ~ $ sudo nano /lib/udev/rules.d/60-libpafe.rules pi@raspberrypi ~ $ udevadm control --reload-rules root privileges required pi@raspberrypi ~ $ sudo udevadm control --reload-rules
なお、/lib/udev/rules.d/60-libpafe.rulesに記載する内容は、改行も含めてlibpafeのページにもある通りにします。
ACTION!="add", GOTO="pasori_rules_end" SUBSYSTEM=="usb_device", GOTO="pasori_rules_start" SUBSYSTEM!="usb", GOTO="pasori_rules_end" LABEL="pasori_rules_start" ATTRS{idVendor}=="054c", ATTRS{idProduct}=="006c", MODE="0664", GROUP="plugdev" ATTRS{idVendor}=="054c", ATTRS{idProduct}=="01bb", MODE="0664", GROUP="plugdev" ATTRS{idVendor}=="054c", ATTRS{idProduct}=="02e1", MODE="0664", GROUP="plugdev" LABEL="pasori_rules_end"
udev設定後に再起動し、sudoなしでも実行できることを確認します。
pi@raspberrypi ~ $ sudo reboot # 再起動後 pi@raspberrypi ~ $ ./pasori/libpafe-0.0.8/tests/pasori_test PaSoRi (RC-S320) firmware version 1.40 Echo test... success EPROM test... success RAM test... success CPU test... success Polling test... success
ctypesを使ったPythonスクリプトの作成
ディレクトリ作成
pi@raspberrypi ~ $ cd pasori pi@raspberrypi ~/pasori $ mkdir py pi@raspberrypi ~/pasori $ cd py
pasori_testをPythonで書く
pasori_testがlibpafeライブラリを使っていることもあり、FeliCaを読む前に、Python + ctypesでpasori_test.c
を実装できるか試してみました。
なお、test関数の中でdata = c_char_p(TEST_DATA)
の部分はPython3は動作しません。Python3ではc_char_p
のかわりにc_wchar_p
を使う必要があります。
- 15.18. ctypes — Pythonのための外部関数ライブラリ — Python 2.7ja1 documentation
- 16.16. ctypes — Pythonのための外部関数ライブラリ — Python 3.4.3 ドキュメント
pasori_test.py
# -*- coding: utf-8 -*- # print時の改行を制御したいので、Python3.xのprint関数を使う from __future__ import print_function from ctypes import * # PASORI_TYPE # libpafe.hの17行目にenumとして定義 PASORI_TYPE_S310 = 0 PASORI_TYPE_S320 = 1 PASORI_TYPE_S330 = 2 TEST_DATA = "test data." def test(libpafe, pasori): # Python2の場合 data = c_char_p(TEST_DATA) # Python3の場合 # data = c_wchar_p(TEST_DATA) data_length = c_int(len(TEST_DATA) - 1) print("Echo test...", end="") # Pythonの場合、数字の0はfalseとして扱われるので、 # 関数が成功した場合はfalseが返ってくることになる if (libpafe.pasori_test_echo(pasori, data, byref(data_length))): print("error!") else: print("success") print("EPROM test... ", end=""); if (libpafe.pasori_test_eprom(pasori)): print("error!") else: print("success") print("RAM test... ", end=""); if (libpafe.pasori_test_ram(pasori)): print("error!") else: print("success") print("CPU test... ", end="") if (libpafe.pasori_test_cpu(pasori)): print("error!") else: print("success") print("Polling test... ", end=""); if (libpafe.pasori_test_polling(pasori)): print("error!") else: print("success") def show_version(libpafe, pasori): v1 = c_int() v2 = c_int() if (libpafe.pasori_version(pasori, byref(v1), byref(v2)) != 0): print("cannot get version") return # RC-S320以外は所持していないので、未テスト pasori_type = libpafe.pasori_type(pasori) if pasori_type == PASORI_TYPE_S320: print("PaSoRi (RC-S320)\n firmware version %d.%02d" % (v1.value, v2.value)) elif pasori_type == PASORI_TYPE_S310: print("PaSoRi (RC-S310)\n firmware version %d.%02d" % (v1.value, v2.value)) elif pasori_type == PASORI_TYPE_S330: print("PaSoRi (RC-S330)\n firmware version %d.%02d" % (v1.value, v2.value)) else: print("unknown hardware.") if __name__ == '__main__': libpafe = cdll.LoadLibrary("/usr/local/lib/libpafe.so") libpafe.pasori_open.restype = c_void_p pasori = libpafe.pasori_open() libpafe.pasori_init(pasori) show_version(libpafe, pasori) type = libpafe.pasori_type(pasori) if (type in {PASORI_TYPE_S310, PASORI_TYPE_S320}): test(libpafe, pasori) libpafe.pasori_close(pasori)
RaspberryPiへのファイル転送
前回同様、scpでファイルを転送します。
..\LibpafePi>scp -r * pi@192.168.0.21:~/pasori/py pi@192.168.0.21's password: LibpafePi.pyproj 100% 2091 2.0KB/s 00:00 pasori_test.py 100% 2514 2.5KB/s 00:00
Pythonスクリプトの実行
実行結果は同じとなり、libpafeがPython + ctypesで扱えることが分かりました。
pi@raspberrypi ~/pasori/py $ python pasori_test.py PaSoRi (RC-S320) firmware version 1.40 Echo test...success EPROM test... success RAM test... success CPU test... success Polling test... success
felica_dump.c
をPythonで書くのは諦めた
同じサンプルコードとしてfelica_dump.c
がありますが、元々のコード*1を追ってみたところ、
など、ctypesに慣れていない自分にはツラそうな内容だったため、今のところは諦めることにしました。
FeliCaのIDmを読むPythonスクリプト
libpafeのREADMEを読んでみると、FeliCaのIDmを読む関数がありました。これなら自分のctypes力でも書けそうでしたので、IDmを読むPythonスクリプトを書いてみました。
idm_reader.py
# -*- coding: utf-8 -*- from __future__ import print_function from ctypes import * # libpafe.hの77行目で定義 FELICA_POLLING_ANY = 0xffff if __name__ == '__main__': libpafe = cdll.LoadLibrary("/usr/local/lib/libpafe.so") libpafe.pasori_open.restype = c_void_p pasori = libpafe.pasori_open() libpafe.pasori_init(pasori) libpafe.felica_polling.restype = c_void_p felica = libpafe.felica_polling(pasori, FELICA_POLLING_ANY, 0, 0) idm = c_uint16() libpafe.felica_get_idm.restype = c_void_p libpafe.felica_get_idm(felica, byref(idm)) # IDmは16進表記 print("%04X" % idm.value) # READMEより、felica_polling()使用後はfree()を使う # なお、freeは自動的にライブラリに入っているもよう libpafe.free(felica) libpafe.pasori_close(pasori)
ファイル転送
..\LibpafePi>scp -r * pi@192.168.0.21:~/pasori/py pi@192.168.0.21's password: LibpafePi.pyproj 100% 2091 2.0KB/s 00:00 idm_reader.py 100% 694 0.7KB/s 00:00 pasori_test.py 100% 2514 2.5KB/s 00:00
実行
FeliCaをパソリの上に置いた上で実行してみると、Python + ctypesでも、felica_dumpを実行した結果と同じIDmを読むことができているようです。
# felica_dump版 pi@raspberrypi ~/pasori/libpafe-0.0.8/tests $ ./felica_dump ... # Manufacture Code = 0101 ... # idm_reader.py版 pi@raspberrypi ~/pasori/py $ python idm_reader.py 0101
ctypesまわりのメモ
次回忘れそうなので、調べたことなどをまとめておきます。
ライブラリの読み込み
from ctypes import * # 変数(ここではlibpafe)でライブラリを参照するため、 # これ以降は `libpafe.pasori_open` のようにしてライブラリの関数を呼び出せる libpafe = cdll.LoadLibrary("/usr/local/lib/libpafe.so")
関数の戻り値がポインタ型の場合の、戻り値の受け取り方法
pasori_open
関数はpsori型のポインタを返すことから、ポインタ型を受け取る必要があります。
この場合、関数名.restype
(例:libpafe.pasori_open.restype)属性で事前に戻り値の型を設定することで、ポインタ型を受け取ることができるようです。
戻り値の型 - 15.18. ctypes — Pythonのための外部関数ライブラリ — Python 2.7ja1 documentation
# restype属性を使ってポインタ型を指定 libpafe.pasori_open.restype = c_void_p # あとは普通に関数を呼び出して受け取る pasori = libpafe.pasori_open()
関数の引数がポインタ型の場合の、関数への引数の渡し方法
pasori_test_echo
関数は引数に3つのポインタを渡しています。
今回は、事前に変数の中にポインタを入れる方法(c_char_pなどを使う)と、関数の引数として渡すときにbyref
を使う方法の2つを試してみました。
ポインタを渡す(または、パラメータの参照渡し) - 15.18. ctypes — Pythonのための外部関数ライブラリ — Python 2.7ja1 documentation
# ***_p関数でポインタにする方法 data = c_char_p(TEST_DATA) # c_***でC言語の型にして、引数として渡す際に`byref`を使う方法 data_length = c_int(len(TEST_DATA) - 1) if (libpafe.pasori_test_echo(pasori, data, byref(data_length))):
PythonとC言語の型について
基本的なものは以下の表にまとまっています。
基本のデータ型 - 15.18. ctypes — Pythonのための外部関数ライブラリ — Python 2.7ja1 documentation
なお、c_uint16などもある型の別名として用意されているようです。
基本データ型 - 15.18. ctypes — Pythonのための外部関数ライブラリ — Python 2.7ja1 documentation
C言語の型(c_***)から値を取り出す時
value
属性を使います。
基本データ型 - value - 15.18. ctypes — Pythonのための外部関数ライブラリ — Python 2.7ja1 documentation
idm = c_uint16() libpafe.felica_get_idm.restype = c_void_p libpafe.felica_get_idm(felica, byref(idm)) print("%04X" % idm.value)
free関数について
felica_polling関数の説明には、
返された felica 型のポインタは不要になったときに free(3) で開放する必要がある。
と書かれていました。
ctypesの場合は、
後者のヘッダファイルがシステム上になければ、 "Python.h" が関数 malloc() 、 free() および realloc() を直接定義します。 http://docs.python.jp/2/extending/extending.html#extending-simpleexample
とのことなので、何もしなくてもライブラリにfreeが生えていたので、そのまま使うことができました。
libpafe.free(felica)
ctypesまわりの、その他参考
- FeliCa の Id を python で読む(その2) — takanory.net
- 良いもの。悪いもの。: Python: PaSoRiでSuicaの履歴を読み出す
- Python - ctypesを使う際の(個人的な)ポイント - Qiita
その他
ソースコード
PythonスクリプトはGitHubに上げておきました。なお、libpafeのサンプルコードを参照して書いたため、ライセンスはlibpafeと同じGPL v2としました。
thinkAmi-sandbox/LibpafeRaspberryPi