ラズベリーパイでICカードのデータを読み取る

じょるブログ

現役理系大学生による大学生に向けた情報サイト

インストール 未分類 電子工作

ラズベリーパイでICカードのデータを読み取る

投稿日:2019年7月16日 更新日:

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

  

    

実装方法

   

必要データのダウンロード・インストール

まずは、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を確認してください。

   

    

参考URL

google ads




google ads




-インストール, 未分類, 電子工作

執筆者:


  1. Chisato より:

    始めまして、今回、記事を参考にさせて頂きながらセットアップをしてみたのですが、libpafe のインストールの部分でつまずいてしまい、どのようにすればよいかご教授頂ければと思い、ご連絡させて頂きました。
    まず、記載されていた
    ./configure
    make
    sudo make install
    の部分をそのまま入力してみたのですが、
    -bash: ./configure: そのようなファイルやディレクトリはありません
    と表示されてしまい、それ以降がうまくいかない状況です。
    ここまでは、記載されていた指示通りに進めることができました。

    何かしらのご回答を頂けると幸いです。

    • joruji より:

      コメントありがとうございます。
      申し訳ございません。記事に記載ミスがありましたので修正いたしました。

      git clone https://github.com/rfujita/libpafe.git
      を実行した後に
      cd libpafe
      が必要でした。

      なのでChisatoさんの現在の状況ですと、
      cd ~/pasori/libpafe
      を実行した後に
      ./configure
      を実行すればできるはずです。

      もしも上記のように実行してもうまくいかない場合は
      sudo apt-get update
      sudo apt-get upgrade

      を実行した上で
      sudo apt-get install libusb-dev
      を実行してみてください。

      それでもうまくいかないようでしたら連絡くださいm(__)m

comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

関連記事

RaspberryPiのセットアップ ② – SSH,VNC(遠隔操作)の設定とファイルサーバーの設定

 この記事では、前回の記事(RaspberryPiのセットアップ ① – 2種類のOSインストール方法と基本設定)に引き続き、ラズパイを遠隔操作するためのSSH・VNCの設定方法と、パソコ …

ラズベリーパイをディスプレイに接続せずSSH設定する方法

    一般的にラズベリーパイをパソコンからSSHで操作しようとした場合は、ラズパイにマウス、キーボード、ディスプレイを接続して、一度ラズパイ上でwi-fiの設定を行う必要があります。 しかし、実家に …

ラズパイでスマートロック作ってみた③ – AmazonDushボタンでラズパイのスマートロックを操作する

大学生の電子工作 ラズパイでスマートロック作ってみたの記事でスマートロックを作成し、現在も使用しているのですが、一つだけ問題点があります。 それは… スマホの充電が切れたら鍵が開けられなくなってしまう …

ラズパイでスマートロック作ってみた② - 扉の開閉状況をGoogleスプレッドシートに記録

以前作成したスマートロックに、扉の開閉状況をGoogleスプレッドシートに記録する、という機能をつけたので、そのやり方について紹介します。   搭載した機能 スイッチで鍵の開閉外部サービス(Googl …

簡単!仮想マシンVMwareでWindowsにLinux(ubuntu)をインストールする方法を徹底解説!

私はWindowsパソコンを使っているのですが、WindowsとLinuxではコマンドが違うため、使いづらいです。Gitを入れることで一部LinuxコマンドがWindowsのコマンドプロンプトで使用で …