じょるブログ

電子工作やプログラミング関連の情報を発信している技術系ブログ

電子工作 インストール ラズベリーパイ

ラズベリーパイで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パッケージのインストールを行います。

  1. cd
  2. mkdir pasori
  3. cd pasori
  4. sudo apt-get install libusb-dev
  5. git clone https://github.com/rfujita/libpafe.git

必要なパッケージがそろったので libpafe のインストールを行います。

  1. cd libpafe
  2. ./configure
  3. make
  4. sudo make install

正しくインストールできているか確認します。

  1. sudo ./tests/pasori_test

以下のように success と表示されれば成功です。

  1. PaSoRi (RC-S320)
  2. firmware version 1.40
  3. Echo test... success
  4. EPROM test... success
  5. RAM test... success
  6. CPU test... success
  7. Polling test... success

   

udevの設定

デバイス管理システムであるudevの設定を行います。

以下のコマンドによりエディターを起動し、 udevルールを作成します。

  1. sudo nano /lib/udev/rules.d/60-libpafe.rules

エディターが起動したら以下の内容を記述してください。

  1. ACTION!="add", GOTO="pasori_rules_end"
  2. SUBSYSTEM=="usb_device", GOTO="pasori_rules_start"
  3. SUBSYSTEM!="usb", GOTO="pasori_rules_end"
  4. LABEL="pasori_rules_start"
  5.  
  6. ATTRS{idVendor}=="054c", ATTRS{idProduct}=="01bb", MODE="0664", GROUP="plugdev"
  7. ATTRS{idVendor}=="054c", ATTRS{idProduct}=="02e1", MODE="0664", GROUP="plugdev"
  8.  
  9. LABEL="pasori_rules_end"

書き込みが終了したら以下のコマンドを実行します。

  1. sudo udevadm control --reload-rules
  2. sudo reboot

再起動後sudoなしでpasori_testを実行できるか確認します。

  1. ./pasori/libpafe/tests/pasori_test

   

    

ICカード読み取り

    

idmの取得・確認

まず、ICカードのidmの確認を行います。

以下のプログラム(pasori_idm.py)を作成してください。

  1. # -*- coding: utf-8 -*-
  2.  
  3. from __future__ import print_function
  4. from ctypes import *
  5.  
  6. # libpafe.hの77行目で定義
  7. FELICA_POLLING_ANY = 0xffff
  8.  
  9. if __name__ == '__main__':
  10.  
  11. libpafe = cdll.LoadLibrary("/usr/local/lib/libpafe.so")
  12.  
  13. libpafe.pasori_open.restype = c_void_p
  14. pasori = libpafe.pasori_open()
  15.  
  16. libpafe.pasori_init(pasori)
  17.  
  18. libpafe.felica_polling.restype = c_void_p
  19. felica = libpafe.felica_polling(pasori, FELICA_POLLING_ANY, 0, 0)
  20.  
  21. idm = c_ulonglong() #←16桁受けとるために変更
  22. libpafe.felica_get_idm.restype = c_void_p
  23. libpafe.felica_get_idm(felica, byref(idm))
  24.  
  25. # IDmは16進表記
  26. print("%016X" % idm.value) #←16桁表示させるために変更
  27.  
  28. # READMEより、felica_polling()使用後はfree()を使う
  29. libpafe.free(felica)
  30.  
  31. libpafe.pasori_close(pasori)

保存場所はどこでも構いません。pasoriの上にICカードを載せてからPython3で上記のプログラムを実行すると16桁のidm番号が出力されます。

スマートロックなどで使用する際に、定期的にidmを取得したい場合は以下のように記述します。

  1. # -*- coding: utf-8 -*-
  2.  
  3. from __future__ import print_function
  4. from time import sleep
  5. from ctypes import *
  6.  
  7. # libpafe.hの77行目で定義
  8. FELICA_POLLING_ANY = 0xffff
  9.  
  10. #許可するidm番号を設定
  11. allow_idm_list = ['1234567890123456', '0123456789ABCDEF']
  12.  
  13. if __name__ == '__main__':
  14.  
  15. libpafe = cdll.LoadLibrary("/usr/local/lib/libpafe.so")
  16. libpafe.pasori_open.restype = c_void_p
  17. pasori = libpafe.pasori_open()
  18. libpafe.pasori_init(pasori)
  19. libpafe.felica_polling.restype = c_void_p
  20. try:
  21. while True:
  22. felica = libpafe.felica_polling(pasori, FELICA_POLLING_ANY, 0, 0)
  23. idm = c_ulonglong()
  24. libpafe.felica_get_idm.restype = c_void_p
  25. libpafe.felica_get_idm(felica, byref(idm))
  26. idm_No = "%016X" % idm.value
  27. print(idm_No)
  28. if idm_No in allow_idm_list:
  29. print('Allow')
  30. elif idm_No == '0000000000000000':
  31. print('カードをタッチしてください')
  32. else:
  33. print('deny')
  34. sleep(1)
  35.  
  36. except KeyboardInterrupt:
  37. print('finished')
  38. libpafe.free(felica)
  39. libpafe.pasori_close(pasori)

allow_idm_listに登録されたICカードがタッチされた場合、’Allow’と出力され、登録されていないICカードがタッチされた場合 ’Deny’と表示されます。

また、何もタッチされていないと ‘カードをタッチしてください’ と出力されます。

出力される文字の下に実行したい処理を記述することで、状況に合わせて任意のコードを実行することもできます。

    

交通履歴・利用履歴の確認

交通ICカード( suica・ PASMO ・ICOCA)で入出場した駅や、運賃、物販の購入履歴なども確認することができます。

  1. # -*- coding: utf-8 -*-
  2.  
  3. from __future__ import print_function
  4. from ctypes import *
  5.  
  6.  
  7. StationCode_CSV = '/PATH/TO/StationCode.csv'
  8.  
  9. POLLING_ANY = 0xffff
  10. POLLING_SUICA = 0x0003
  11. POLLING_EDY = 0xfe00
  12.  
  13. SERVICE_SUICA = 0x090f
  14. SERVICE_EDY = 0x170f
  15.  
  16. # 構造体の代わりとなるクラスの定義
  17. class felica_block_info(Structure):
  18. _fields_ = [
  19. ("service", c_uint16),
  20. ("mode", c_uint8),
  21. ("block", c_uint16)
  22. ]
  23.  
  24.  
  25. # 端末種
  26. TERMINAL = {3: "精算機",
  27. 4: "携帯型端末",
  28. 5: "車載端末",
  29. 7: "券売機",
  30. 8: "券売機",
  31. 9: "入金機",
  32. 18: "券売機",
  33. 20: "券売機等",
  34. 21: "券売機等",
  35. 22: "改札機",
  36. 23: "簡易改札機",
  37. 24: "窓口端末",
  38. 25: "窓口端末",
  39. 26: "改札端末",
  40. 27: "携帯電話",
  41. 28: "乗継精算機",
  42. 29: "連絡改札機",
  43. 31: "簡易入金機",
  44. 70: "VIEW ALTTE",
  45. 72: "VIEW ALTTE",
  46. 199: "物販端末",
  47. 200: "自販機" }
  48.  
  49. # 処理
  50. PROCESS = { 1: "運賃支払(改札出場)",
  51. 2: "チャージ",
  52. 3: "券購(磁気券購入)",
  53. 4: "精算",
  54. 5: "精算 (入場精算)",
  55. 6: "窓出 (改札窓口処理)",
  56. 7: "新規 (新規発行)",
  57. 8: "控除 (窓口控除)",
  58. 13: "バス (PiTaPa系)",
  59. 15: "バス (IruCa系)",
  60. 17: "再発 (再発行処理)",
  61. 19: "支払 (新幹線利用)",
  62. 20: "入A (入場時オートチャージ)",
  63. 21: "出A (出場時オートチャージ)",
  64. 31: "入金 (バスチャージ)",
  65. 35: "券購 (バス路面電車企画券購入)",
  66. 70: "物販",
  67. 72: "特典 (特典チャージ)",
  68. 73: "入金 (レジ入金)",
  69. 74: "物販取消",
  70. 75: "入物 (入場物販)",
  71. 198: "物現 (現金併用物販)",
  72. 203: "入物 (入場現金併用物販)",
  73. 132: "精算 (他社精算)",
  74. 133: "精算 (他社入場精算)" }
  75.  
  76. def read_station_code(fname):
  77. global STATION_CODE
  78. STATION_CODE = {}
  79. data = []
  80. with open(fname) as f:
  81. for line in f:
  82. if line[0] in ("0", "1", "2"):
  83. data.append(line.strip().split(","))
  84. line = f.readline()
  85. for d in data:
  86. STATION_CODE[tuple(map(lambda x: int(x, 16), d[0:3]))] = (d[4], d[5])
  87.  
  88. def read_felica():
  89. libpafe = cdll.LoadLibrary("/usr/local/lib/libpafe.so")
  90.  
  91. libpafe.pasori_open.restype = c_void_p
  92. pasori = libpafe.pasori_open()
  93.  
  94. libpafe.pasori_init(pasori)
  95.  
  96. libpafe.felica_polling.restype = c_void_p
  97. felica = libpafe.felica_polling(pasori, POLLING_ANY, 0, 0)
  98.  
  99. # 履歴の読み出し
  100. int_array16 = c_uint8 * 16
  101. data = []
  102. d = int_array16()
  103. info = felica_block_info(c_uint16(0x090f), c_uint8(0), c_uint16(0))
  104. i = 1
  105. c_i = c_int(i)
  106. while libpafe.felica_read(felica, byref(c_i), byref(info), byref(d))==0:
  107. print(c_i)
  108. data.append(string_at(pointer(d), 16))
  109. print(string_at(pointer(d), 16))
  110. i += 1
  111. c_i = c_int(i)
  112.  
  113. libpafe.free(felica)
  114. libpafe.pasori_close(pasori)
  115.  
  116. return data
  117.  
  118. def parse_data(d, prev=-1):
  119. term = (d[0]) # 端末種.
  120. proc = (d[1]) # 処理.
  121. date = (d[4:6]) # 日付.
  122. year = (date[0] >> 1) + 2000
  123. month = ((date[0] & 1) << 3) + (date[1] >> 5)
  124. day = date[1] & (1<<5) - 1
  125. in_line = (d[6]) # 入線区.
  126. in_sta = (d[7]) # 入駅順.
  127. out_line = (d[8]) # 出線区.
  128. out_sta = (d[9]) # 出駅順.
  129. balance = (d[10:12]) # 残高.
  130. num = ( d[12:15]) # 連番.
  131. region = (d[15]) # リージョン.
  132.  
  133. print("%4d年%02d月%02d日" % (year, month, day))
  134. if proc in (70, 73, 74, 75, 198, 203): # 物販.
  135. hour = in_line >> 3
  136. min = ((in_line & 7) << 3) + (in_sta >> 5)
  137. sec = (in_sta & 0x1f) << 1
  138. print("%02d時%02d分%02d秒" % (hour, min, sec))
  139. print("買物")
  140. elif proc in (13, 15, 31, 35): # バス.
  141. out_line = ( d[6:8])
  142. out_sta = ( d[8:10])
  143. print("バス")
  144. else:
  145. if region == 0:
  146. if in_line < 0x80: area = 0 # JR線.
  147. else: area = 1 # 関東公営・私鉄.
  148. else: area = 2 # 関西公営・私鉄.
  149. if in_line not in (0xc7, 0xc8, 0x05):
  150. if (area in STATION_CODE) and (in_line in STATION_CODE) and (in_sta in STATION_CODE):
  151. print("%s駅" % STATION_CODE[(area, in_line, in_sta)][1])
  152. else: print("不明 {0} {1} {2}".format(area,in_line,in_sta))
  153. if (area in STATION_CODE) and (out_line in STATION_CODE) and (out_sta in STATION_CODE):
  154. if not (area == 0 and out_line == 0 and out_sta == 0):
  155. print("%s駅" % STATION_CODE[(area, out_line, out_sta)][1])
  156. else: print("不明")
  157. account = (balance[1] << 8) + balance[0]
  158. charge = prev - account
  159. if prev < 0: print("---円")
  160. elif charge > 0: print("%d円" % charge)
  161. elif charge < 0: print("%+d円" % -charge)
  162. print("%d円" % account)
  163. if term in TERMINAL: print(TERMINAL[term])
  164. else: print("不明")
  165. if proc in PROCESS: print(PROCESS[proc])
  166. else: print("不明")
  167. print("")
  168.  
  169. return account
  170.  
  171. if __name__ == "__main__":
  172. read_station_code(StationCode_CSV)
  173. data = read_felica()
  174. prev = -1
  175. for d in data[::-1]:
  176. prev = parse_data(d, prev)

上記のプログラムを使用する際、StationCodeデータが必要になります。こちらのサイトからダウンロードできます。

上記のコードの StationCode_CSV変数 にダウンロードした StationCodeファイルのパスを記述してください。

実行すると以下のような情報が出力されます。

  1. 20170715
  2. 東京駅
  3. 渋谷駅
  4. 料金:194
  5. 残高:1560
  6. 改札機
  7. 運賃支払(改札出場)
  8.  
  9.  
  10. 20170715
  11. 182452
  12. 買物
  13. 料金:120
  14. 残高:1440
  15. 自販機
  16. 物販

※地下鉄のデータは載っていません。地下鉄の情報も参照したい場合は他のサイトから駅データをダウンロードしてください。

  

その他のデータの取得

他にも PMm (製造パラメタ) なども取得できます。詳しくはlibpafeのREADMEを確認してください。

   

    

参考URL

google ads




google ads




-電子工作, インストール, ラズベリーパイ

執筆者:


  1. Chisato より:

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

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

    • joruji より:

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

      1. git clone https://github.com/rfujita/libpafe.git

      を実行した後に

      1. cd libpafe

      が必要でした。

      なのでChisatoさんの現在の状況ですと、

      1. cd ~/pasori/libpafe

      を実行した後に

      1. ./configure

      を実行すればできるはずです。

      もしも上記のように実行してもうまくいかない場合は

      1. sudo apt-get update
      2. sudo apt-get upgrade

      を実行した上で

      1. sudo apt-get install libusb-dev

      を実行してみてください。

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

comment

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

CAPTCHA


関連記事

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

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

ESP-WROOM-32の基本的な使い方 初期設定や書込み方法、Lチカについて画像付きで徹底解説

  この記事ではESP-WROOM-32の基本的な使い方について解説します。 ESP-WROOM-32は低価格(1000円以下)で買うことができるWiFi/Bluetoothモジュールです。 上の画像 …

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

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

Alexa, Google HomeをRasberryPiと連携する

この記事ではスマートリモコンを作ってみたで作成した、スマートリモコンをAlexaやGoogle Assistantと連携するために利用した外部サービスについて記載します。     Amazon Ech …

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

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

S