ラズベリーパイで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 )

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

   

    

参考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

joruji へ返信する コメントをキャンセル

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

CAPTCHA


関連記事

壊れたイヤホンを半田ごてを使って直してみた – 修理方法解説

   長年使用していたイヤホンが壊れてしまったので、半田ごてを使用して直してみました。この記事ではイヤホンの直し方について解説します。 (イヤホンの構造によって多少直し方は異なる場合があります。) 今 …

初めての大学生の電子工作 スマートリモコンを作ってみた

 タイトル通り、初めて電子工作をやってみました。電子工作をやったことがないという方にもこの記事を読めば作れるように、つまづいたとこなども丁寧に説明していきます。もしわからないことなどがございましたら気 …

大学生の電子工作 スマートリモコン(プログラム)

この記事ではスマートリモコンを作ってみたで紹介したスマートリモコンのプログラムを記載していきます。回路はスマートリモコン(回路)に記載してあります。今回作成するプログラムは以下の4つです。 (LED点 …

ラズパイに4TBの外付けHDDを接続しNAS(ファイルサーバー)を構築してみた

私は256GBのSSDが搭載されているWindowsパソコンを使用しているのですが、最近空き容量が30GBくらいになってきてしまいました。空き容量がギリギリになると色々と不具合が発生してくるとどこかで …

電子工作 ラズパイで玄関モニター(兼防犯カメラ)作ってみた

今回は前回のスマートリモコンに続き、2回目のIoTデバイス制作です。玄関モニター兼防犯カメラを作ってみました。作成難易度はスマートリモコンよりも簡単ですので皆さんも是非作ってみてください! 玄関モニタ …




関連記事