Monday, April 30, 2007

Ruby で Suica を覗いてみる

以下を参考に Suica のデータを具体的に解析してみましょう。

最初の1バイトが 0x1B だとクレジット入金、0x07 or 0x08だと入金、0x46だとサンクスチャージの入金で、0x16が自動改札乗降、0xC7が購買のようです。5バイト目から2バイトが日付で、先頭から7ビットが年、4ビットが月、残り5ビットが日のようです。(ここが面倒でしたね。パズルみたい)7バイト目からの2バイトが入った駅、続く2バイトが出た駅がコードで入っています。駅のコードは、路線コード/駅コードの組合わせで、有志によるデータベースが公開されています。IC SFCard Fan DB Srevice このソフトではコードからの変換はしていません。12バイト目から2バイトが残金(リトルエンディアン)です。

160100020e98e376e37d2e2200008000 を例として詳しく見ていくと以下のようになります。

16 010002 0e98 e376 e37d 2e22 00008000
1 2-4 5,6 7,8 9,10 11,12 13-16
1 バイト(タイプ)
  • 1b クレジット入金
  • 07,08 入金
  • 46 サンクスチャージ入金
  • 16 自動改札乗降
  • c7 購買
2-4 バイト
不明。私の場合、すべて 010002 になっている。
5,6 バイト(日付)
0e98 を2進数表記にすると 0000 1110 1001 1000

年(7bit)/月(4bit)/日(5bit) なので 0000111 0100 11000 となりこれを10進数にして 7 4 24

年に2000を足すことで 2007年4月24日 という日付が求められる。

7,8 バイト(入場駅)
前半1バイトが 線区コード で後半1バイトが 駅順コード と言うらしい。

これは、IC SFCard Fan のページで調べられる。SOAP による Web Service公開されているのでこれを利用するとよさそう。

9,10 バイト(出場駅)
同上
11,12 バイト(残額)
リトルエンディアン(2e22)らしいのでビッグエンディアン(222e)に変換して10進数にすると 8750円 となる。
13-16 バイト(連番?)
15バイト目が 1 or 2 づつインクリメントしていることが確認できるが、それが意味するところは分からない。

これを Ruby で実装すると以下のような感じになります。

def Pasori.parse_suica_raw_value data
  d = "%016b" % data[8, 4].hex
  {
    :type => data[0, 2],
    :date => Time.local(d[0, 7].to_i(2) + 2000, d[7, 4].to_i(2), d[11, 5].to_i(2)),
    :in   => data[12, 4],
    :out  => data[16, 4],
    :yen  => data[20, 2].hex + (data[22, 2].hex << 8),
  }
end

こちらで作成したプログラムに上記関数を適用します。

dump_suica_2.rb
require 'pasori'
Pasori.felica_raw_values Pasori::POLLING_SUICA, Pasori::SERVICE_SUICA, true do |data|
    d = Pasori.parse_suica_raw_value data
    str = sprintf "%s %s %s %s %5s", d[:type], d[:date].strftime('%Y/%m/%d'), d[:in], d[:out], d[:yen]
    puts str
end

実行結果(dump_suica.rb)

% ruby dump_suica.rb
160100020e9bf101f20c661c00008e00
160100020e9bf102c5026a1d00008c00
160100020e9be37de532781e00008a00
160100020e9ae532e37d361f00008800
160100020e9ae37de532f41f00008600
160100020e99e532e37db22000008400
160100020e99e37de532702100008200
160100020e98e376e37d2e2200008000
1d0100020e98f102f104a62200007e00
160100020e98c502f102502300007c00
160100020e98f102c5025e2400007a00
160100020e98e37de5326c2500007800
160100020e97e355e37d2a2600007600
160100020e97e43ee3552a2600007400
160100020e97e355e43ee82600007200
160100020e97e37de37a062700007000
160100020e9625020102a62700006e00
160100020e9501022502462800006c00
160100020e95e37de37ae62800006a00
1b023f000e9500000000862900006800

実行結果(dump_suica_2.rb)

% ruby dump_suica_2.rb 
16 2007/04/27 f101 f20c  7270
16 2007/04/27 f102 c502  7530
16 2007/04/27 e37d e532  7800
16 2007/04/26 e532 e37d  7990
16 2007/04/26 e37d e532  8180
16 2007/04/25 e532 e37d  8370
16 2007/04/25 e37d e532  8560
16 2007/04/24 e376 e37d  8750
1d 2007/04/24 f102 f104  8870
16 2007/04/24 c502 f102  9040
16 2007/04/24 f102 c502  9310
16 2007/04/24 e37d e532  9580
16 2007/04/23 e355 e37d  9770
16 2007/04/23 e43e e355  9770
16 2007/04/23 e355 e43e  9960
16 2007/04/23 e37d e37a  9990
16 2007/04/22 2502 0102 10150
16 2007/04/21 0102 2502 10310
16 2007/04/21 e37d e37a 10470
1b 2007/04/21 0000 0000 10630

こちらで実行したものと同じような結果を得ることができるようになりました。あとは何とか駅名を日本語表示にしてみたいものです。

関連URL