今回はSONYのICカードリーダー( RC-S320 )を使用して、suicaなどのICカードを読み取り、 idm(ICカードの固有番号、ICタグ)や残高、交通履歴などを取得する方法についてご紹介します。
このICカードリーダーをラズベリーパイと組み合わせることで、スマートロックの開錠を行えるようになったり、タイムカードの代わりとして使用したりできます!
ICカードリーダー はUSBにを挿入しただけでは使えないので、libpafe を使用してICカードを読み取れるようにします。 libpafe の felica_read という関数を用いることでICカードの様々な情報を取得することができますが、こちらの関数はC言語で記述されているため、Pythonのctypesという関数を用いることで、Python上でこの felica_read 関数を扱えるようにします。
※ libpafe は RC-S320 とRC-S330 にのみ対応しています。 RC-S330 以降を使用する場合は nfcpy で実装することになるので、実装方法が異なります。( RC-S330 は libpafe nfcpy どちらでも大丈夫です。)
Amazonで売っているICカードリーダーの中でこの RC-S320 が一番安く、中古で500円以下で買えたので今回はこちらを使用します。
SONY RC-S320 非接触ICカードリーダ/ライタ PaSoRi 「パソリ」
目次
実行環境
- ラズベリーパイ3 B+ ( raspberry pi zero wh でも動作確認済み)
- ICカードリーダー ( RC-S320 )
Raspberry Pi4 ModelB 4GB ラズベリーパイ4 技適対応品
実装方法
必要データのダウンロード・インストール
まずは、libpafeのダウンロードと、libpafeをインストールする際に必要となるlibusbパッケージのインストールを行います。
- cd
- mkdir pasori
- cd pasori
- sudo apt-get install libusb-dev
- git clone https://github.com/rfujita/libpafe.git
必要なパッケージがそろったので libpafe のインストールを行います。
- cd libpafe
- ./configure
- make
- sudo make install
正しくインストールできているか確認します。
- sudo ./tests/pasori_test
以下のように success と表示されれば成功です。
- PaSoRi (RC-S320)
- firmware version 1.40
- Echo test... success
- EPROM test... success
- RAM test... success
- CPU test... success
- Polling test... success
udevの設定
デバイス管理システムであるudevの設定を行います。
以下のコマンドによりエディターを起動し、 udevルールを作成します。
- sudo nano /lib/udev/rules.d/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
再起動後sudoなしでpasori_testを実行できるか確認します。
- ./pasori/libpafe/tests/pasori_test
ICカード読み取り
idmの取得・確認
まず、ICカードのidmの確認を行います。
以下のプログラム(pasori_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() #←16桁受けとるために変更
- libpafe.felica_get_idm.restype = c_void_p
- libpafe.felica_get_idm(felica, byref(idm))
-
- # IDmは16進表記
- print("%016X" % idm.value) #←16桁表示させるために変更
-
- # READMEより、felica_polling()使用後はfree()を使う
- libpafe.free(felica)
-
- libpafe.pasori_close(pasori)
保存場所はどこでも構いません。pasoriの上にICカードを載せてからPython3で上記のプログラムを実行すると16桁のidm番号が出力されます。
スマートロックなどで使用する際に、定期的にidmを取得したい場合は以下のように記述します。
- # -*- coding: utf-8 -*-
-
- from __future__ import print_function
- from time import sleep
- from ctypes import *
-
- # libpafe.hの77行目で定義
- FELICA_POLLING_ANY = 0xffff
-
- #許可するidm番号を設定
- allow_idm_list = ['1234567890123456', '0123456789ABCDEF']
-
- 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
-
- try:
- while True:
- 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_No = "%016X" % idm.value
- print(idm_No)
- if idm_No in allow_idm_list:
- print('Allow')
- elif idm_No == '0000000000000000':
- print('カードをタッチしてください')
- else:
- print('deny')
- sleep(1)
-
- except KeyboardInterrupt:
- print('finished')
- libpafe.free(felica)
- libpafe.pasori_close(pasori)
allow_idm_listに登録されたICカードがタッチされた場合、’Allow’と出力され、登録されていないICカードがタッチされた場合 ’Deny’と表示されます。
また、何もタッチされていないと ‘カードをタッチしてください’ と出力されます。
出力される文字の下に実行したい処理を記述することで、状況に合わせて任意のコードを実行することもできます。
交通履歴・利用履歴の確認
交通ICカード( suica・ PASMO ・ICOCA)で入出場した駅や、運賃、物販の購入履歴なども確認することができます。
- # -*- coding: utf-8 -*-
-
- from __future__ import print_function
- from ctypes import *
-
-
- StationCode_CSV = '/PATH/TO/StationCode.csv'
-
- POLLING_ANY = 0xffff
- POLLING_SUICA = 0x0003
- POLLING_EDY = 0xfe00
-
- SERVICE_SUICA = 0x090f
- SERVICE_EDY = 0x170f
-
- # 構造体の代わりとなるクラスの定義
- class felica_block_info(Structure):
- _fields_ = [
- ("service", c_uint16),
- ("mode", c_uint8),
- ("block", c_uint16)
- ]
-
-
- # 端末種
- TERMINAL = {3: "精算機",
- 4: "携帯型端末",
- 5: "車載端末",
- 7: "券売機",
- 8: "券売機",
- 9: "入金機",
- 18: "券売機",
- 20: "券売機等",
- 21: "券売機等",
- 22: "改札機",
- 23: "簡易改札機",
- 24: "窓口端末",
- 25: "窓口端末",
- 26: "改札端末",
- 27: "携帯電話",
- 28: "乗継精算機",
- 29: "連絡改札機",
- 31: "簡易入金機",
- 70: "VIEW ALTTE",
- 72: "VIEW ALTTE",
- 199: "物販端末",
- 200: "自販機" }
-
- # 処理
- PROCESS = { 1: "運賃支払(改札出場)",
- 2: "チャージ",
- 3: "券購(磁気券購入)",
- 4: "精算",
- 5: "精算 (入場精算)",
- 6: "窓出 (改札窓口処理)",
- 7: "新規 (新規発行)",
- 8: "控除 (窓口控除)",
- 13: "バス (PiTaPa系)",
- 15: "バス (IruCa系)",
- 17: "再発 (再発行処理)",
- 19: "支払 (新幹線利用)",
- 20: "入A (入場時オートチャージ)",
- 21: "出A (出場時オートチャージ)",
- 31: "入金 (バスチャージ)",
- 35: "券購 (バス路面電車企画券購入)",
- 70: "物販",
- 72: "特典 (特典チャージ)",
- 73: "入金 (レジ入金)",
- 74: "物販取消",
- 75: "入物 (入場物販)",
- 198: "物現 (現金併用物販)",
- 203: "入物 (入場現金併用物販)",
- 132: "精算 (他社精算)",
- 133: "精算 (他社入場精算)" }
-
- def read_station_code(fname):
- global STATION_CODE
- STATION_CODE = {}
- data = []
- with open(fname) as f:
- for line in f:
- if line[0] in ("0", "1", "2"):
- data.append(line.strip().split(","))
- line = f.readline()
-
-
- for d in data:
- STATION_CODE[tuple(map(lambda x: int(x, 16), d[0:3]))] = (d[4], d[5])
-
- def read_felica():
- 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, POLLING_ANY, 0, 0)
-
- # 履歴の読み出し
- int_array16 = c_uint8 * 16
- data = []
- d = int_array16()
- info = felica_block_info(c_uint16(0x090f), c_uint8(0), c_uint16(0))
-
- i = 1
- c_i = c_int(i)
- while libpafe.felica_read(felica, byref(c_i), byref(info), byref(d))==0:
- print(c_i)
- data.append(string_at(pointer(d), 16))
- print(string_at(pointer(d), 16))
- i += 1
- c_i = c_int(i)
-
- libpafe.free(felica)
- libpafe.pasori_close(pasori)
-
- return data
-
- def parse_data(d, prev=-1):
- term = (d[0]) # 端末種.
- proc = (d[1]) # 処理.
- date = (d[4:6]) # 日付.
- year = (date[0] >> 1) + 2000
- month = ((date[0] & 1) << 3) + (date[1] >> 5)
- day = date[1] & (1<<5) - 1
- in_line = (d[6]) # 入線区.
- in_sta = (d[7]) # 入駅順.
- out_line = (d[8]) # 出線区.
- out_sta = (d[9]) # 出駅順.
- balance = (d[10:12]) # 残高.
- num = ( d[12:15]) # 連番.
- region = (d[15]) # リージョン.
-
- print("%4d年%02d月%02d日" % (year, month, day))
- if proc in (70, 73, 74, 75, 198, 203): # 物販.
- hour = in_line >> 3
- min = ((in_line & 7) << 3) + (in_sta >> 5)
- sec = (in_sta & 0x1f) << 1
- print("%02d時%02d分%02d秒" % (hour, min, sec))
- print("買物")
- elif proc in (13, 15, 31, 35): # バス.
- out_line = ( d[6:8])
- out_sta = ( d[8:10])
- print("バス")
- else:
- if region == 0:
- if in_line < 0x80: area = 0 # JR線.
- else: area = 1 # 関東公営・私鉄.
- else: area = 2 # 関西公営・私鉄.
- if in_line not in (0xc7, 0xc8, 0x05):
- if (area in STATION_CODE) and (in_line in STATION_CODE) and (in_sta in STATION_CODE):
- print("%s駅" % STATION_CODE[(area, in_line, in_sta)][1])
- else: print("不明 {0} {1} {2}".format(area,in_line,in_sta))
- if (area in STATION_CODE) and (out_line in STATION_CODE) and (out_sta in STATION_CODE):
- if not (area == 0 and out_line == 0 and out_sta == 0):
- print("%s駅" % STATION_CODE[(area, out_line, out_sta)][1])
- else: print("不明")
- account = (balance[1] << 8) + balance[0]
- charge = prev - account
- if prev < 0: print("---円")
- elif charge > 0: print("%d円" % charge)
- elif charge < 0: print("%+d円" % -charge)
- print("%d円" % account)
- if term in TERMINAL: print(TERMINAL[term])
- else: print("不明")
- if proc in PROCESS: print(PROCESS[proc])
- else: print("不明")
- print("")
-
- return account
-
- if __name__ == "__main__":
- read_station_code(StationCode_CSV)
- data = read_felica()
- prev = -1
- for d in data[::-1]:
- prev = parse_data(d, prev)
上記のプログラムを使用する際、StationCodeデータが必要になります。こちらのサイトからダウンロードできます。
上記のコードの StationCode_CSV変数 にダウンロードした StationCodeファイルのパスを記述してください。
実行すると以下のような情報が出力されます。
- 2017年07月15日
- 東京駅
- 渋谷駅
- 料金:194円
- 残高:1560円
- 改札機
- 運賃支払(改札出場)
-
-
- 2017年07月15日
- 18時24分52秒
- 買物
- 料金:120円
- 残高:1440円
- 自販機
- 物販
※地下鉄のデータは載っていません。地下鉄の情報も参照したい場合は他のサイトから駅データをダウンロードしてください。
その他のデータの取得
他にも PMm (製造パラメタ) なども取得できます。詳しくはlibpafeのREADMEを確認してください。
始めまして、今回、記事を参考にさせて頂きながらセットアップをしてみたのですが、libpafe のインストールの部分でつまずいてしまい、どのようにすればよいかご教授頂ければと思い、ご連絡させて頂きました。
まず、記載されていた
./configure
make
sudo make install
の部分をそのまま入力してみたのですが、
-bash: ./configure: そのようなファイルやディレクトリはありません
と表示されてしまい、それ以降がうまくいかない状況です。
ここまでは、記載されていた指示通りに進めることができました。
何かしらのご回答を頂けると幸いです。
コメントありがとうございます。
申し訳ございません。記事に記載ミスがありましたので修正いたしました。
を実行した後に
が必要でした。
なのでChisatoさんの現在の状況ですと、
を実行した後に
を実行すればできるはずです。
もしも上記のように実行してもうまくいかない場合は
を実行した上で
を実行してみてください。
それでもうまくいかないようでしたら連絡くださいm(__)m