2008年4月23日水曜日

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

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

以前、PaSoRiでSuicaの履歴を読み出すという記事を書いたけど、酷いバグを見つけた。物販で購入時の時刻を間違えるというもの。言い訳になるが、つい最近まで古いSuicaを使っていて、駅の自販機などでSuicaが使えず、テストしていなかったのだ。申し訳ない。

と云うわけで、修正したソースを公開しておく。バグだけ修正しても面白くないので、少し改良した。以前は残高しか出なかったが、使用した金額も併せて表示するようにした。また、以下に示す出力のように日時が先頭に来るように変更した。等々。

2008年xx月xx日 ○○駅 ××駅 540円 4160円 改札機 運賃支払(改札出場) 2008年xx月xx日 ○○駅 +2000円 6160円 券売機等 チャージ 2008年xx月xx日 xx時xx分xx秒 買物 120円 6040円 自販機 物販

以下、ソースコード。まだテストが足りないのでバグがあるかもしれない。見つけ次第、訂正する。

#!/usr/bin/env python # -*- coding: shift_jis -*- """ read_felica.py version 0.02 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, prev=-1): 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]) # リージョン. 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 = 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 in_line 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)): 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 TERMINAL.has_key(term): print TERMINAL[term], else: print "不明", if PROCESS.has_key(proc): print PROCESS[proc], else: print "不明", print return account def main(): read_station_code("StationCode.csv") data = read_felica() prev = -1 for d in data[::-1]: prev = parse_data(d, prev) if __name__ == "__main__": main()

0 コメント: