Python
FeliCa
libpafe

PythonでSuicaの残高を取得する(libpafe使用)

More than 1 year has passed since last update.

PythonでSuicaの残高を取得する(libpafe使用)

  • 手元に中古で買ったFelicaリーダのRC-S320があったので,Pythonを使って動かせないか試してみた.
  • ちょっとした備忘録的なメモです.
  • そのうちRaspberry piとかで何かと組み合わせてみる予定.

動作環境

  • OS: LMDE 2 Betsy
  • FeliCaリーダ: Sony RC-S320
  • Pythonのバージョン: 2.7.9

ライブラリ

  • nfcpyの存在を知ったが,RC-S320には対応していない.
  • libpafeが対応していた.
    • C言語のライブラリとして使える.
    • Ruby上ならlibpafe-rubyが使える.

環境設定

  1. libusbのインストール
    • $ sudo apt-get install libusb-dev
  2. GitHubからlibpafeをcloneする
    • $ git clone https://github.com/rfujita/libpafe.git
  3. コンパイル&インストール

    $ cd libpafe
    $ ./configure
    $ make
    $ sudo make install
    
  4. 動作確認

    $ cd libpafe
    $ ./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の設定をしていないので,多分sudoがいるかもしれない (2017/8/14 追記)
  5. udevの設定

    • 60-libpafe.rulesを作成
      • $ sudo vi /lib/udev/rules.d/60-libpafe.rules
    • 以下の内容を記述
    60-libpafe.rules
    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}=="01bb", MODE="0664", GROUP="plugdev"
    ATTRS{idVendor}=="054c", ATTRS{idProduct}=="02e1", MODE="0664", GROUP="plugdev"
    
    LABEL="pasori_rules_end"
    
    • $ sudo udevadm control --reload-rulesを実行
    • $ sudo rebootで再起動する.

ICカードのIDmを取得する

read_idm.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_ulonglong()
    libpafe.felica_get_idm.restype = c_void_p
    libpafe.felica_get_idm(felica, byref(idm))

    # IDmは16進表記
    print("IDm:", "%016X" % idm.value)

    # READMEより、felica_polling()使用後はfree()を使う
    # なお、freeは自動的にライブラリに入っているもよう
    libpafe.free(felica)

    libpafe.pasori_close(pasori)
  • libpafeはC言語用のライブラリだが,Pythonのctypesを使うことでC言語の関数がPython上で使えるようになる.
    • felica_get_idmを使う.
      • int felica_get_idm(felica *f, uint8 *idm);
        • f : felica_pollingで取得したfelica型のポインタ
        • idm : IDmを格納するためのポインタ

Suicaの残高を取得する

  • Suicaの残高を表示するのはどうやるか?

    • ICカード内の情報を取ってきて,その中から残高の情報を拾ってくる必要がある
    • Suicaのデータ構造まとめ: suica - FeliCa Library Wiki - FeliCa Library - OSDN
      • 10-11のあたりのデータを取ってくれば良さそう.
  • libpafeのfelica_readという関数で諸々の情報を取ってくることができるので,それを使う.
    参考: 続けるブログ: ラズベリーパイがICカードの残高をしゃべるよ

    • ↑こちらはC言語で書かれているため,ctypesを使ってPython上でfelica_readを呼び出せるように書き換える
    • 引数が構造体のポインタを示していたりするので,あらかじめ定義して調整する
read_balance.py
# -*- coding: utf-8 -*-

from __future__ import print_function
from ctypes import *

# libpafe.hの77行目で定義
FELICA_POLLING_ANY = 0xffff

# 構造体の代わりとなるクラスの定義
class felica_block_info(Structure):
    _fields_ = [
        ("service", c_uint16),
        ("mode", c_uint8),
        ("block", c_uint16)
    ]

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)

    # Cのint型配列の定義(長さ16)
    int_array16 = c_uint8 * 16

    # 応答データ
    data = int_array16()
    # サービスコードのリスト
    info = felica_block_info(c_uint16(0x090f), c_uint8(0), c_uint16(0))
    for i in range(0, 32):
        c_i = c_int(i)
        libpafe.felica_read(felica, byref(c_i), byref(info), byref(data))
        if (data[1] > 0) or (data[2] > 0):
            print("残高:", data[11] * 256 + data[10], "円")
            break

    libpafe.free(felica)

    libpafe.pasori_close(pasori)

  • 同じ構造になっているからか,ICOCAとtoicaでもちゃんと使えた.

参考