Showing posts with label PaSoRi. Show all posts
Showing posts with label PaSoRi. Show all posts

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

Wednesday, April 25, 2007

Ruby で PaSoRi 使ってみる

Ruby にはダイナミックリンカへのインターフェースが用意されているのでこれを利用してみます。

DL::Importable を利用しヘッダファイルを参考にしながらライブラリ関数に対してラッパーメソッドを定義していきます。

pasori.rb
require 'dl/import'

module Pasori
  extend DL::Importable
  dlload '/usr/local/lib/libpasori.dylib'

  typealias 'uint8', 'unsigned char'
  typealias 'uint16', 'unsigned int'
  #typealias 'uint16', 'unsigned short int'

  # libpasori.h
  extern 'pasori* pasori_open(char*)'
  extern 'void pasori_close(pasori*)'
  extern 'int pasori_send(pasori*,uint8*,uint8,int)'
  extern 'int pasori_recv(pasori*,uint8*,uint8,int)'

  POLLING_ANY = 0xffff
  POLLING_SUICA = 0x0003
  POLLING_EDY = 0xfe00

  SERVICE_SUICA = 0x090f
  SERVICE_EDY = 0x170f

  # libpasori_command.h
  extern 'int pasori_init(pasori*)'
  extern 'int pasori_write(pasori*,uint8,uint8)'
  extern 'int pasori_read(pasori*,uint8,uint8)'
  extern 'felica* felica_polling(pasori*,uint16,uint8,uint8)'
  extern 'int felica_read_without_encryption02(felica*,int,int,uint8,uint8*)'
end

これで、libpasori で提供されている関数郡を Ruby から透過的に利用できるようになりました。

次に、lptest.c を参考に各値をパースして出力している部分を省略し、生データ(16バイト ビックエンディアン)で返す関数を書いてみます。

pasori.rb 続き
module Pasori
  class << self
    def felica_raw_values systemcode, servicecode, little_endian = false
      values = []
      b = Array.new(4).to_ptr
      psr = pasori_open ""
      pasori_init psr
      flc = felica_polling psr, systemcode, 0, 0
      i = 0
      while felica_read_without_encryption02(flc, servicecode, 0, i, b) == 0
        row = b.to_a('I')
        data = ""
        row.size.times do |j|
          if little_endian
            4.times { |k| data += sprintf "%02x", (row[j].to_i >> (8 * k)) & 0xff }
          else
            data += sprintf "%08x", row[j].to_i & 0xffffffff
          end
        end
        yield data if block_given?
        values << data
        i += 1
      end
      pasori_close psr
      values
    end
  end
end

これを利用し、データを表示するだけのプログラムを書いてみます。

dump_suica.rb
require 'pasori'
Pasori.felica_raw_values Pasori::POLLING_SUICA, Pasori::SERVICE_SUICA, true do |data|
    puts data
end

それでは、実際に PaSoRi に Suica / PASMO を置いてこのコードを実行してみましょう。

% ruby dump_suica.rb
160100020e98e376e37d2e2200008000
1d0100020e98f102f104a62200007e00
160100020e98c502f102502300007c00
160100020e98f102c5025e2400007a00
160100020e98e37de5326c2500007800
160100020e97e355e37d2a2600007600
160100020e97e43ee3552a2600007400
160100020e97e355e43ee82600007200
160100020e97e37de37a062700007000
160100020e9625020102a62700006e00
160100020e9501022502462800006c00
160100020e95e37de37ae62800006a00
1b023f000e9500000000862900006800
160100020e95e532e37d760200006700
160100020e94e37de532340300006500
160100020e93e376e37df20300006300
1d0100020e93f102f1046a0400006100
160100020e93d204e532140500005f00
160100020e92e001e0074a0600005d00
160100020e92e532e534e00600005b00

最新20履歴のデータが表示されました。
次回は、これを人間が見ても分かる表示にしていきましょう。

関連URL

Saturday, April 21, 2007

libpasori の共有ライブラリ化

libpasori 共有ライブラリ化なんて、gcc のオプションをちょこっと変更してあげればOKでしょ?くらいに思っていたんだけど、何だか Mac の gcc はちょっと勝手が違うらしい。

Using static and shared libraries across platforms

共有ライブラリの拡張子も libhoge.so じゃなくて libhoge.dylib なんだそうだ。いままで気が付かなかった。gcc のオプションいろいろ試してみるがなかなかうまくいかない。もういいやと諦めかけたころ、ふと Xcode の存在を思いだした。Mac Switch 歴 10ヶ月、いままで全く使ったことは無かったし必要も無かったけれども、これを機会に試してみよう。

MacPorts を使うため Developer Tools は導入済み。

Xcode を起動し、ファイル -> 新規プロジェクト... を選択。

それらしいプロジェクト発見!

こんな画面が開きます。

libpasori のソースコードを追加しましょう。

左ペイン「グループとファイル」にある、Source を右クリック -> 追加 -> 既存のファイル... を選択します。

以下のファイルを選択し、追加します。

ディスティネーショングループのフォルダに項目をコピーするをチェックします。

こんな感じになります。

プロジェクト -> プロジェクト情報を編集 -> ビルド にてビルド設定できるようです。

何を設定すべきかは、libpasori 添付のビルドスクリプトを参考にしましょう。

buildline-libusb.sh
#!/bin/sh
gcc -o lpdump `libusb-config --cflags` -D__LIBPASORI_WITH_LIBUSB lptest.c libpasori_command.c libpasori_com_libusb.c libpasori_polling.c `libusb-config --libs`

バッククォートの部分を実行してみます。

% libusb-config --cflags
-I/opt/local/include
% libusb-config --libs
-L/opt/local/lib -lusb -Wl,-framework -Wl,IOKit -Wl,-framework -Wl,CoreFoundation -Wl,-prebind

これらを参考にして、以下のように設定します。

必要ライブラリ・フレームワークを追加します。

プロジェクト(pasori) を右クリック -> 追加 -> 既存のファイル... から以下のファイルを追加します。

/opt/local/lib/libusb.dylib

プロジェクト(pasori) を右クリック -> 追加 -> 既存フレームワーク... から以下のフレームワークを追加します。

/System/Library/Frameworks/IOKit.framework
/System/Library/Frameworks/CoreFoundation.framework

すると、こうなります。

それではビルドしてみましょう。金槌アイコンクリックです。

無事完成したようです。

これで Ruby から libpasori を利用する準備は完了です。

関連URL

Ruby で PaSoRi を使いたい

libpasori に付属している Suica の情報を読み取るサンプルプログラム lptest.c 等によると、一件の履歴情報は16バイトに収められているらしい。

あとはこの情報を解析したりファイルやDBに保存したり駅コードから駅名を調べるためにネットに接続したりしていきたいのだが、これらをCで書くのは面倒くさいので、FeliCaから各16バイトの情報を読み取る部分だけをC(libpasori)にやらせて、あとの処理はLightWeightな言語で扱えるようにしていきたい。

ということで、ますは libpasori を共有ライブラリ化し、それをRubyで利用する方向で実現していこう。

関連URL

Tuesday, April 17, 2007

Mac で PaSoRi

それでは早速、libpasori の動作確認をしてみる。

まず、libusb に依存しているのでこれを導入する必要がある。 ソースをダウンロードする前に MacPorts での存在チェック。

% port search libusb
libusb                         devel/libusb   0.1.12       Library providing access to USB devices

おお、あった。一応オプションも確認。

% port variants libusb
libusb has no variants

とくに無いようなのでそのままインストール。

% sudo port install libusb
--->  Fetching libusb
--->  Verifying checksum(s) for libusb
--->  Extracting libusb
--->  Configuring libusb
--->  Building libusb with target all
--->  Staging libusb into destroot
--->  Installing libusb 0.1.12_0
--->  Activating libusb 0.1.12_0
--->  Cleaning libusb

もちろん、libusb からソースダウンロード&コンパイルでもOK。

次に、ライブラリパスを登録しなければいけないのだが、Mac版 ldconfig は無いのかな?本流の作法が分からないがLD_LIBRARY_PATH的環境変数を設定することにする。

Mac では DYLD_LIBRARY_PATH と DYLD_FALLBACK_LIBRARY_PATH というのがあるらしい。

「DYLD_LIBRARY_PATH」はここにライブラリがあったらデフォルトのパスよりも優先して使う、「DYLD_FALLBACK_LIBRARY_PATH」はデフォルトのパスにライブラリが見つからない場合ここを探して見つかれば使う、という意味。詳細は Manual Page For dyld(1) を参照のこと。

ということらしいので、ここでは DYLD_FALLBACK_LIBRARY_PATH を設定する。

% export DYLD_FALLBACK_LIBRARY_PATH=/opt/local/lib

これで準備環境なので、libpasori の動作を確認してみよう。

サンプルプログラムが二つ添付されているので、それぞれ実行してみる。

まずは、lpdump

% tar jxvf libpasori02.tar.bz2
% cd libpasori/src
% ./buildline-libusb.sh
% ./lpdump
# lpdump : Tue Apr 17 05:36:34 2007
# --- IDm info (FeliCa) ---
# Manufacture Date = 2001/2/7
#               SN = 15938
# Manufacture Code = 0101
#      Equip. Code = 0105
# FELICA SYSTEM_CODE = 0003
# card IDm = xxxxxxxxxxxxxxxx
# card PMm = xxxxxxxxxxxxxxxx
... snip ...

まさにダンプデータで中身は理解不能。

次に、lptest

% cp buildline-libusb.sh buildline-libusb-lptest.sh
% perl -i -pe 's/lpdump/lptest/g' buildline-libusb-lptest.sh
% ./buildline-libusb-lptest.sh
% ./lptest
lptest : start
read test
[00] : 08 03 00000E81 E3-3E -> 00-00      0000 -     0 Yen        00013E00
[01] : 16 01 00020E81 E3-55 -> E3-5D      0A00 -    10 Yen        00013D00
[02] : 1A 06 000E0E81 F0-3A -> F0-3A      C800 -   200 Yen        00013B00
[03] : 16 01 00020E81 E3-7D -> E3-7A      C800 -   200 Yen        00013900
[04] : 16 01 00020E7F F2-06 -> F2-0A      6801 -   360 Yen        00013700
... snip ...

あ、0円。こちらは、比較的分かり易いSuicaの利用履歴となっています。

ただ、日付や入出場駅名がコード化されているようなので、これを解析する必要があります。

そこらへんは、以下を参考に。

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

とにかく、Mac でもなんとか利用できることは分かりました。
次は、これを活用できるようにしていきたいと思います。

関連URL

Monday, April 16, 2007

PaSoRi 購入

SONY RC-S320

Suica/Pasmo の履歴データを保存したい。

http://libpasori.sourceforge.jp/

これ使ってなんとかなりそう。

商品の詳細を見る