2008年3月28日金曜日

Python: PaSoRiでSuicaの履歴を読み出す

このブログ記事をはてなブックマークに追加

Rubyを使ってPaSoRi経由でSuicaの乗車履歴を取得し、GoogleMapsやGoogleEarthで表示するって記事でPaSoRiなるものを知った。Edyもオサイフケータイも使ってないし、FeliCaについてはまったく興味がなかったのでスルーしてたんだけどこれは面白そう。早速、SONY RC-S320 非接触ICカードリーダ/ライタ PaSoRi 「パソリ」を買ってきていじってみた。因みに値段は約3000円。

まず、PaSoRiを読み出すためのライブラリとしてfelicaliblibpasoriなどがあったのでこれらを調べてみると、どうやらPythonで利用するためにはCで拡張モジュールを書かないとダメそうで、ちょっと面倒だなと思っていたが、FeliCa の Id を python で読むというページを見つけて、Pythonのctypesモジュールを使えば拡張無しでできそうなことを知る。

で、上記のサイトなどを参考にざざっとプログラムを書いてみた。ライブラリはWindowsを使っていることもあってfelicalibを選択。プログラムを実行させるには、このライブラリと駅コード情報が必要となる。路線・駅コード一覧・登録から、Excel形式ファイルを落としてきて、それの駅線・駅コード一覧のページをCSV形式に変換しておく(StationCode.csv)。店舗情報やバス情報の取得については、コードも増えるし面倒なので割愛。でも簡単に入れられるハズ。

実行すると以下のような履歴をすべて出力する。

改札機 運賃支払(改札出場) 2008年01月14日 赤羽駅 王子駅 8920円 改札機 運賃支払(改札出場) 2008年01月14日 王子駅 赤羽駅 9070円

ああ、面白かった。以下、ソース。

追記: バグ修正改良版については、Python: PaSoRiでSuicaの履歴を読み出す・その後を参照のこと。

#!/usr/bin/env python # -*- coding: shift_jis -*- """ read_felica.py by nox Suicaの履歴を出力するプログラム. """ from ctypes import * POLLING_ANY = 0xffff POLLING_SUICA = 0x0003 POLLING_EDY = 0xfe00 SERVICE_SUICA = 0x090f SERVICE_EDY = 0x170f # 端末種. 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 = [l.strip().split(",") for l in file(fname) if l[0] in ("0", "1", "2")] for d in data: STATION_CODE[tuple(map(lambda x: int(x, 16), d[0:3]))] = (d[4], d[5]) def read_felica(): flib = cdll.felicalib flib.pasori_open.restype = c_void_p pasori = flib.pasori_open() flib.pasori_init(pasori) flib.felica_polling.restype = c_void_p felica = flib.felica_polling(pasori, POLLING_SUICA, 0, 0) # Suicaを読む. # 履歴の読み出し. data = [] d = create_string_buffer(16) i = 0 while flib.felica_read_without_encryption02(felica, SERVICE_SUICA, 0, i, d) == 0: data.append(string_at(pointer(d), 16)) i += 1 flib.pasori_close(pasori) return data def parse_data(d): term = ord(d[0]) # 端末種. proc = ord(d[1]) # 処理. date = map(ord, d[4:6]) # 日付. year = (date[0] >> 1) + 2000 month = ((date[0] & 1) << 3) + (date[1] >> 5) day = date[1] & (1<<5) - 1 in_line = ord(d[6]) # 入線区. in_sta = ord(d[7]) # 入駅順. out_line = ord(d[8]) # 出線区. out_sta = ord(d[9]) # 出駅順. balance = map(ord, d[10:12]) # 残高. num = map(ord, d[12:15]) # 連番. region = ord(d[15]) # リージョン. if TERMINAL.has_key(term): print TERMINAL[term], else: print "不明", if PROCESS.has_key(proc): print PROCESS[proc], else: print "不明", print "%4d年%02d月%02d日" % (year, month, day), if proc in (70, 73, 74, 75, 198, 203): # 物販. hour = in_line >> 3 min = (in_line & (2<<3) - 1) << 1 + in_sta >> 7 sec = (in_sta & (2<<5) - 1) * 2 print "%02d時%02d分%02d秒" % (hour, min, sec), print "物販", elif proc in (13, 15, 31, 35): # バス. out_line = map(ord, d[6:8]) out_sta = map(ord, d[8:10]) print "バス", else: if region == 0: if in_line < 0x80: area = 0 # JR線. else: area = 1 # 関東公営・私鉄. else: area = 2 # 関西公営・私鉄. if term not in (0xc7, 0xc8, 0x05): if STATION_CODE.has_key((area, in_line, in_sta)): print "%s駅" % STATION_CODE[(area, in_line, in_sta)][1], else: print "不明", if STATION_CODE.has_key((area, out_line, out_sta)): print "%s駅" % STATION_CODE[(area, out_line, out_sta)][1], else: print "不明", print "%d円" % (balance[1] * 256 + balance[0]), print def main(): read_station_code("StationCode.csv") data = read_felica() for d in data: parse_data(d) if __name__ == "__main__": main()

0 コメント: