はじめに
2007年にMacにPaSoRiを接続してSuicaやPASMOの履歴情報を読み出すプログラムを書いて公開しましたが、その後ほったらかしていました。
Sony さんが非接触ICカードリーダー/ライター PaSoRi®(パソリ) 「RC-S390」を発売し、iPhone/iPad 等で利用ができるようになっていいのですが、折角のWi-Fi 対応の PaSoRi なので、Macでも利用できればと思って心待ちにしているものの、未だにSonyさんからアプリの提供はありません。
古い情報ですが、Suica(含PASMO等)のデータ構造等を開発者の皆さんに公開して、Mac版を作ってもらいたいという他力本願の投稿です。
Suicaのデータ構造
この情報は、自分の 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バイト:残金(リトルエンディアン)
MacでSuicaを読むプログラム
2007年に公開したプログラムです。
殆どの機能はネット上に公開されているlibpasoril]に依存していますが現時点では公開されていません。(残念です)
更にlibpasoriはlibusbを利用しています。(Wi-Fi利用なら別のライブラリーが必要でしょう)
ソースはここに再掲しますが、詳細は Macで Suica や PASMO の履歴を読む
を御覧ください。
#include <stdio.h>
#include <stdlib.h>
#include "libpasori.h"
int
main(void){
pasori* p;
felica* f;
int i;
int m;
int wk, wk2;
uint8 b[16];
p = pasori_open(NULL);
if(!p){
printf("error\n");
exit(-1);
}
pasori_init(p);
f = felica_polling(p,0x0003,0,0);
printf("*** Suica Value ***\n");
i=0;
while(!felica_read_without_encryption02(f,0x090f,0,i,b)){
printf("[%02d] : ",i);
printf("%02X ",b[0]);
printf("%02X ",b[1]);
printf("%02X",b[2]);
printf("%02X ",b[3]);
wk=b[4]>>1;
wk=wk&0x7F;
printf("%02d/",wk);
wk=b[4]&0x01;
wk=wk<<3;
wk2=b[5]>>5;
wk2=wk2&0x07;
wk=wk+wk2;
printf("%02d/",wk);
wk=b[5]&0x1F;
printf("%02d ",wk);
printf("%02X-%02X -> ",b[6],b[7]);
printf("%02X-%02X ",b[8],b[9]);
printf(" % 6d Yen ",b[11]*256+b[10]);
printf("%02X%02X%02X%02X\n",b[12],b[13],b[14],b[15]);
i++;
}
pasori_close(p);
return 0;
}
更に、これを利用して見やすくフォーマットするAppleScriptのソースも再掲しておきます。
-- 「SuicaValue.scpt」
-- PaSoRiでSuicaの履歴を抽出して表示するスクリプトです。
-- Suicaを読むアプリケーション「SuicaValue」はホームディレクトリ直下に置いて実行権を与えておいて下さい。
-- 最初に駅名の検索/表示をするかを問い合わせます。駅名の検索/表示ではインターネットに接続して検索しますので、
-- ネットに接続されていることが必要です。(商品の購入では表示されません)
-- 結果を表示後にOKボタンを押すと、表示結果のテキストをクリップボードにコピーして終了します。
-- このスクリプトでは、IC SFCardFanのページ(http://www.denno.net/SFCardFan/)にあるSOAPのサービスを
-- 利用して駅名と店名を検索しています。(サービスの提供に感謝します)
property SOAP_Endpoint_URL : "http://www.denno.net/SFCardFan/soapserver.php"
property SOAP_app : "soap"
property method_name_ST : "getStationName"
property method_name_SH : "getShopName"
property method_namespace_URI : ""
property SOAP_action : ""
display dialog " 駅名/店名検索をしますか?" & return & "(インターネットに接続します)" buttons {"Yes", "No"} default button 2
set SerchOn to button returned of result
-- SuicaValue のある場所を指定
set cmdPath to "$HOME/SuicaValue"
do shell script cmdPath
set SuicaValue to result as text
set dataCount to the number of paragraphs of SuicaValue
set sortedData to ""
repeat with i from 0 to dataCount - 2
set eachData to paragraph (dataCount - i) of SuicaValue
set Proc to word 2 of eachData
if Proc = "00" then
else
set lineData to characters 19 thru 26 of eachData & " "
set zankin to (characters 43 thru 55 of eachData) as text
if Proc = "1B" or Proc = "07" or Proc = "08" or Proc = "46" then
set lineData to (lineData & " 入金後 " & word 1 of zankin as string) & "円"
set lineData to lineData & " 場所:" & (characters 28 thru 32 of eachData)
else if Proc = "C7" or Proc = "C8" then
set lineData to (lineData & " 購入後 " & word 1 of zankin as string) & "円"
set lineData to lineData & " 店舗:" & (characters 28 thru 32 of eachData)
set lineData to lineData & "ー" & (characters 37 thru 41 of eachData)
if SerchOn = "Yes" then
set shop to my shopname(Proc as string, (characters 37 thru 41 of eachData) as string)
set lineData to lineData & " " & shop
end if
else
set lineData to lineData & " 乗車後 " & word 1 of zankin & "円"
set lineData to lineData & " 区間:" & (characters 28 thru 32 of eachData)
set lineData to lineData & "~" & (characters 37 thru 41 of eachData)
-- 駅名の検索
if SerchOn = "Yes" then
set fromStation to my stationname((characters 28 thru 32 of eachData) as string)
set toStation to my stationname((characters 37 thru 41 of eachData) as string)
set lineData to lineData & " " & fromStation & " --> " & toStation
end if
end if
set sortedData to sortedData & lineData & (ASCII character 13)
end if
end repeat
display dialog sortedData
set the clipboard to sortedData as string
-- 処理終了(結果をクリップボードにコピー)
-- 駅名の検索
on stationname(LSCode)
set LC to Hex2Dec((characters 1 thru 2 of LSCode) as string)
set SC to Hex2Dec((characters 4 thru 5 of LSCode) as string)
if LC < 128 then
set AC to "0"
else
set AC to "1"
end if
set the method_parameters to {AreaCode:AC, LineCode:LC, StationCode:SC}
copy my SOAP_call_ST(SOAP_Endpoint_URL, method_name_ST, method_namespace_URI, method_parameters, SOAP_action) to {call_indicator, call_result}
if the call_indicator is false then
beep
display dialog "An error occurred." & return & return & call_result buttons {"Cancel"} default button 1
else
try
set findName to value of item 6 of item 1 of every item of call_result
on error
set findName to "駅名不明"
end try
return findName
end if
end stationname
-- 店舗名の検索
on shopname(TermCode, LSCode)
set TC to Hex2Dec(TermCode)
set LC to Hex2Dec((characters 1 thru 2 of LSCode) as string)
set SC to Hex2Dec((characters 4 thru 5 of LSCode) as string)
set AC to "1"
set the method_parameters to {AreaCode:AC, TerminalCode:TC, LineCode:LC, StationCode:SC}
copy my SOAP_call_SH(SOAP_Endpoint_URL, method_name_SH, method_namespace_URI, method_parameters, SOAP_action) to {call_indicator, call_result}
if the call_indicator is false then
beep
display dialog "An error occurred." & return & return & call_result buttons {"Cancel"} default button 1
else
try
set findName to value of item 5 of item 1 of every item of call_result
set findName to findName & "、" & value of item 6 of item 1 of every item of call_result
on error
set findName to "店名不明"
end try
return findName
end if
end shopname
-- SOAP 検索(駅名)
on SOAP_call_ST(SOAP_Endpoint_URL, method_name_ST, method_namespace_URI, method_parameters, SOAP_action)
try
using terms from application "http://www.apple.com/"
tell application SOAP_Endpoint_URL
set this_result to call soap {method name:method_name_ST, method namespace uri:method_namespace_URI, parameters:method_parameters, SOAPAction:SOAP_action}
end tell
end using terms from
return {true, this_result}
on error error_message number error_number
if the error_number is -916 then set the error_message to "インターネットに接続できませんでした。"
return {false, error_message}
end try
end SOAP_call_ST
-- SOAP 検索(店舗)
on SOAP_call_SH(SOAP_Endpoint_URL, method_name_SH, method_namespace_URI, method_parameters, SOAP_action)
try
using terms from application "http://www.apple.com/"
tell application SOAP_Endpoint_URL
set this_result to call soap {method name:method_name_SH, method namespace uri:method_namespace_URI, parameters:method_parameters, SOAPAction:SOAP_action}
end tell
end using terms from
return {true, this_result}
on error error_message number error_number
if the error_number is -916 then set the error_message to "インターネットに接続できませんでした。"
return {false, error_message}
end try
end SOAP_call_SH
-- 16進を10進に変換(2桁のみ)
on Hex2Dec(HEX)
set ans to ((offset of (character 1 of HEX) in "0123456789ABCDEF") - 1) * 16
set ans to ans + (offset of (character 2 of HEX) in "0123456789ABCDEF") - 1
return ans
end Hex2Dec