今回は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